blob: f889c43499cf233d8ece6cb2ddcae280a65bd6ab [file] [log] [blame]
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/objects/js-objects.h"
#include "src/api/api-arguments-inl.h"
#include "src/common/globals.h"
#include "src/date/date.h"
#include "src/execution/arguments.h"
#include "src/execution/frames.h"
#include "src/execution/isolate.h"
#include "src/handles/handles-inl.h"
#include "src/handles/maybe-handles.h"
#include "src/heap/factory-inl.h"
#include "src/heap/heap-inl.h"
#include "src/heap/memory-chunk.h"
#include "src/init/bootstrapper.h"
#include "src/logging/counters.h"
#include "src/logging/log.h"
#include "src/objects/allocation-site-inl.h"
#include "src/objects/api-callbacks.h"
#include "src/objects/arguments-inl.h"
#include "src/objects/dictionary.h"
#include "src/objects/elements.h"
#include "src/objects/field-type.h"
#include "src/objects/fixed-array.h"
#include "src/objects/heap-number.h"
#include "src/objects/js-array-buffer.h"
#include "src/objects/js-array-inl.h"
#include "src/objects/layout-descriptor.h"
#include "src/objects/lookup.h"
#include "src/objects/objects-inl.h"
#ifdef V8_INTL_SUPPORT
#include "src/objects/js-break-iterator.h"
#include "src/objects/js-collator.h"
#endif // V8_INTL_SUPPORT
#include "src/objects/js-collection.h"
#ifdef V8_INTL_SUPPORT
#include "src/objects/js-date-time-format.h"
#include "src/objects/js-display-names.h"
#endif // V8_INTL_SUPPORT
#include "src/objects/js-generator-inl.h"
#ifdef V8_INTL_SUPPORT
#include "src/objects/js-list-format.h"
#include "src/objects/js-locale.h"
#include "src/objects/js-number-format.h"
#include "src/objects/js-plural-rules.h"
#endif // V8_INTL_SUPPORT
#include "src/objects/js-promise.h"
#include "src/objects/js-regexp-inl.h"
#include "src/objects/js-regexp-string-iterator.h"
#ifdef V8_INTL_SUPPORT
#include "src/objects/js-relative-time-format.h"
#include "src/objects/js-segment-iterator.h"
#include "src/objects/js-segmenter.h"
#include "src/objects/js-segments.h"
#endif // V8_INTL_SUPPORT
#include "src/objects/js-weak-refs.h"
#include "src/objects/map-inl.h"
#include "src/objects/module.h"
#include "src/objects/oddball.h"
#include "src/objects/property-cell.h"
#include "src/objects/property-descriptor.h"
#include "src/objects/property.h"
#include "src/objects/prototype-info.h"
#include "src/objects/prototype.h"
#include "src/objects/shared-function-info.h"
#include "src/objects/transitions.h"
#include "src/strings/string-builder-inl.h"
#include "src/strings/string-stream.h"
#include "src/utils/ostreams.h"
#include "src/wasm/wasm-objects.h"
namespace v8 {
namespace internal {
// static
Maybe<bool> JSReceiver::HasProperty(LookupIterator* it) {
for (; it->IsFound(); it->Next()) {
switch (it->state()) {
case LookupIterator::NOT_FOUND:
case LookupIterator::TRANSITION:
UNREACHABLE();
case LookupIterator::JSPROXY:
return JSProxy::HasProperty(it->isolate(), it->GetHolder<JSProxy>(),
it->GetName());
case LookupIterator::INTERCEPTOR: {
Maybe<PropertyAttributes> result =
JSObject::GetPropertyAttributesWithInterceptor(it);
if (result.IsNothing()) return Nothing<bool>();
if (result.FromJust() != ABSENT) return Just(true);
break;
}
case LookupIterator::ACCESS_CHECK: {
if (it->HasAccess()) break;
Maybe<PropertyAttributes> result =
JSObject::GetPropertyAttributesWithFailedAccessCheck(it);
if (result.IsNothing()) return Nothing<bool>();
return Just(result.FromJust() != ABSENT);
}
case LookupIterator::INTEGER_INDEXED_EXOTIC:
// TypedArray out-of-bounds access.
return Just(false);
case LookupIterator::ACCESSOR:
case LookupIterator::DATA:
return Just(true);
}
}
return Just(false);
}
// static
Maybe<bool> JSReceiver::HasOwnProperty(Handle<JSReceiver> object,
Handle<Name> name) {
if (object->IsJSModuleNamespace()) {
PropertyDescriptor desc;
return JSReceiver::GetOwnPropertyDescriptor(object->GetIsolate(), object,
name, &desc);
}
if (object->IsJSObject()) { // Shortcut.
Isolate* isolate = object->GetIsolate();
LookupIterator::Key key(isolate, name);
LookupIterator it(isolate, object, key, LookupIterator::OWN);
return HasProperty(&it);
}
Maybe<PropertyAttributes> attributes =
JSReceiver::GetOwnPropertyAttributes(object, name);
MAYBE_RETURN(attributes, Nothing<bool>());
return Just(attributes.FromJust() != ABSENT);
}
Handle<Object> JSReceiver::GetDataProperty(LookupIterator* it,
AllocationPolicy allocation_policy) {
for (; it->IsFound(); it->Next()) {
switch (it->state()) {
case LookupIterator::INTERCEPTOR:
case LookupIterator::NOT_FOUND:
case LookupIterator::TRANSITION:
UNREACHABLE();
case LookupIterator::ACCESS_CHECK:
// Support calling this method without an active context, but refuse
// access to access-checked objects in that case.
if (!it->isolate()->context().is_null() && it->HasAccess()) continue;
V8_FALLTHROUGH;
case LookupIterator::JSPROXY:
it->NotFound();
return it->isolate()->factory()->undefined_value();
case LookupIterator::ACCESSOR:
// TODO(verwaest): For now this doesn't call into AccessorInfo, since
// clients don't need it. Update once relevant.
it->NotFound();
return it->isolate()->factory()->undefined_value();
case LookupIterator::INTEGER_INDEXED_EXOTIC:
return it->isolate()->factory()->undefined_value();
case LookupIterator::DATA:
return it->GetDataValue(allocation_policy);
}
}
return it->isolate()->factory()->undefined_value();
}
// static
Maybe<bool> JSReceiver::HasInPrototypeChain(Isolate* isolate,
Handle<JSReceiver> object,
Handle<Object> proto) {
PrototypeIterator iter(isolate, object, kStartAtReceiver);
while (true) {
if (!iter.AdvanceFollowingProxies()) return Nothing<bool>();
if (iter.IsAtEnd()) return Just(false);
if (PrototypeIterator::GetCurrent(iter).is_identical_to(proto)) {
return Just(true);
}
}
}
namespace {
bool HasExcludedProperty(
const ScopedVector<Handle<Object>>* excluded_properties,
Handle<Object> search_element) {
// TODO(gsathya): Change this to be a hashtable.
for (int i = 0; i < excluded_properties->length(); i++) {
if (search_element->SameValue(*excluded_properties->at(i))) {
return true;
}
}
return false;
}
V8_WARN_UNUSED_RESULT Maybe<bool> FastAssign(
Handle<JSReceiver> target, Handle<Object> source,
const ScopedVector<Handle<Object>>* excluded_properties, bool use_set) {
// Non-empty strings are the only non-JSReceivers that need to be handled
// explicitly by Object.assign.
if (!source->IsJSReceiver()) {
return Just(!source->IsString() || String::cast(*source).length() == 0);
}
Isolate* isolate = target->GetIsolate();
// If the target is deprecated, the object will be updated on first store. If
// the source for that store equals the target, this will invalidate the
// cached representation of the source. Preventively upgrade the target.
// Do this on each iteration since any property load could cause deprecation.
if (target->map().is_deprecated()) {
JSObject::MigrateInstance(isolate, Handle<JSObject>::cast(target));
}
Handle<Map> map(JSReceiver::cast(*source).map(), isolate);
if (!map->IsJSObjectMap()) return Just(false);
if (!map->OnlyHasSimpleProperties()) return Just(false);
Handle<JSObject> from = Handle<JSObject>::cast(source);
if (from->elements() != ReadOnlyRoots(isolate).empty_fixed_array()) {
return Just(false);
}
Handle<DescriptorArray> descriptors(map->instance_descriptors(kRelaxedLoad),
isolate);
bool stable = true;
for (InternalIndex i : map->IterateOwnDescriptors()) {
HandleScope inner_scope(isolate);
Handle<Name> next_key(descriptors->GetKey(i), isolate);
Handle<Object> prop_value;
// Directly decode from the descriptor array if |from| did not change shape.
if (stable) {
DCHECK_EQ(from->map(), *map);
DCHECK_EQ(*descriptors, map->instance_descriptors(kRelaxedLoad));
PropertyDetails details = descriptors->GetDetails(i);
if (!details.IsEnumerable()) continue;
if (details.kind() == kData) {
if (details.location() == kDescriptor) {
prop_value = handle(descriptors->GetStrongValue(i), isolate);
} else {
Representation representation = details.representation();
FieldIndex index = FieldIndex::ForPropertyIndex(
*map, details.field_index(), representation);
prop_value = JSObject::FastPropertyAt(from, representation, index);
}
} else {
LookupIterator it(isolate, from, next_key,
LookupIterator::OWN_SKIP_INTERCEPTOR);
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, prop_value, Object::GetProperty(&it), Nothing<bool>());
stable = from->map() == *map;
descriptors.PatchValue(map->instance_descriptors(kRelaxedLoad));
}
} else {
// If the map did change, do a slower lookup. We are still guaranteed that
// the object has a simple shape, and that the key is a name.
LookupIterator it(isolate, from, next_key, from,
LookupIterator::OWN_SKIP_INTERCEPTOR);
if (!it.IsFound()) continue;
DCHECK(it.state() == LookupIterator::DATA ||
it.state() == LookupIterator::ACCESSOR);
if (!it.IsEnumerable()) continue;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, prop_value, Object::GetProperty(&it), Nothing<bool>());
}
if (use_set) {
// The lookup will walk the prototype chain, so we have to be careful
// to treat any key correctly for any receiver/holder.
LookupIterator::Key key(isolate, next_key);
LookupIterator it(isolate, target, key);
Maybe<bool> result =
Object::SetProperty(&it, prop_value, StoreOrigin::kNamed,
Just(ShouldThrow::kThrowOnError));
if (result.IsNothing()) return result;
if (stable) {
stable = from->map() == *map;
descriptors.PatchValue(map->instance_descriptors(kRelaxedLoad));
}
} else {
if (excluded_properties != nullptr &&
HasExcludedProperty(excluded_properties, next_key)) {
continue;
}
// 4a ii 2. Perform ? CreateDataProperty(target, nextKey, propValue).
// This is an OWN lookup, so constructing a named-mode LookupIterator
// from {next_key} is safe.
LookupIterator it(isolate, target, next_key, LookupIterator::OWN);
CHECK(JSObject::CreateDataProperty(&it, prop_value, Just(kThrowOnError))
.FromJust());
}
}
return Just(true);
}
} // namespace
// static
Maybe<bool> JSReceiver::SetOrCopyDataProperties(
Isolate* isolate, Handle<JSReceiver> target, Handle<Object> source,
const ScopedVector<Handle<Object>>* excluded_properties, bool use_set) {
Maybe<bool> fast_assign =
FastAssign(target, source, excluded_properties, use_set);
if (fast_assign.IsNothing()) return Nothing<bool>();
if (fast_assign.FromJust()) return Just(true);
Handle<JSReceiver> from = Object::ToObject(isolate, source).ToHandleChecked();
// 3b. Let keys be ? from.[[OwnPropertyKeys]]().
Handle<FixedArray> keys;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, keys,
KeyAccumulator::GetKeys(from, KeyCollectionMode::kOwnOnly, ALL_PROPERTIES,
GetKeysConversion::kKeepNumbers),
Nothing<bool>());
if (!from->HasFastProperties() && target->HasFastProperties() &&
!target->IsJSGlobalProxy()) {
// JSProxy is always in slow-mode.
DCHECK(!target->IsJSProxy());
// Convert to slow properties if we're guaranteed to overflow the number of
// descriptors.
int source_length;
if (from->IsJSGlobalObject()) {
source_length = JSGlobalObject::cast(*from)
.global_dictionary()
.NumberOfEnumerableProperties();
} else if (V8_DICT_MODE_PROTOTYPES_BOOL) {
source_length =
from->property_dictionary_ordered().NumberOfEnumerableProperties();
} else {
source_length =
from->property_dictionary().NumberOfEnumerableProperties();
}
if (source_length > kMaxNumberOfDescriptors) {
JSObject::NormalizeProperties(isolate, Handle<JSObject>::cast(target),
CLEAR_INOBJECT_PROPERTIES, source_length,
"Copying data properties");
}
}
// 4. Repeat for each element nextKey of keys in List order,
for (int i = 0; i < keys->length(); ++i) {
Handle<Object> next_key(keys->get(i), isolate);
// 4a i. Let desc be ? from.[[GetOwnProperty]](nextKey).
PropertyDescriptor desc;
Maybe<bool> found =
JSReceiver::GetOwnPropertyDescriptor(isolate, from, next_key, &desc);
if (found.IsNothing()) return Nothing<bool>();
// 4a ii. If desc is not undefined and desc.[[Enumerable]] is true, then
if (found.FromJust() && desc.enumerable()) {
// 4a ii 1. Let propValue be ? Get(from, nextKey).
Handle<Object> prop_value;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, prop_value,
Runtime::GetObjectProperty(isolate, from, next_key), Nothing<bool>());
if (use_set) {
// 4c ii 2. Let status be ? Set(to, nextKey, propValue, true).
Handle<Object> status;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, status,
Runtime::SetObjectProperty(isolate, target, next_key, prop_value,
StoreOrigin::kMaybeKeyed,
Just(ShouldThrow::kThrowOnError)),
Nothing<bool>());
} else {
if (excluded_properties != nullptr &&
HasExcludedProperty(excluded_properties, next_key)) {
continue;
}
// 4a ii 2. Perform ! CreateDataProperty(target, nextKey, propValue).
LookupIterator::Key key(isolate, next_key);
LookupIterator it(isolate, target, key, LookupIterator::OWN);
CHECK(JSObject::CreateDataProperty(&it, prop_value, Just(kThrowOnError))
.FromJust());
}
}
}
return Just(true);
}
String JSReceiver::class_name() {
ReadOnlyRoots roots = GetReadOnlyRoots();
if (IsFunction()) return roots.Function_string();
if (IsJSArgumentsObject()) return roots.Arguments_string();
if (IsJSArray()) return roots.Array_string();
if (IsJSArrayBuffer()) {
if (JSArrayBuffer::cast(*this).is_shared()) {
return roots.SharedArrayBuffer_string();
}
return roots.ArrayBuffer_string();
}
if (IsJSArrayIterator()) return roots.ArrayIterator_string();
if (IsJSDate()) return roots.Date_string();
if (IsJSError()) return roots.Error_string();
if (IsJSGeneratorObject()) return roots.Generator_string();
if (IsJSMap()) return roots.Map_string();
if (IsJSMapIterator()) return roots.MapIterator_string();
if (IsJSProxy()) {
return map().is_callable() ? roots.Function_string()
: roots.Object_string();
}
if (IsJSRegExp()) return roots.RegExp_string();
if (IsJSSet()) return roots.Set_string();
if (IsJSSetIterator()) return roots.SetIterator_string();
if (IsJSTypedArray()) {
#define SWITCH_KIND(Type, type, TYPE, ctype) \
if (map().elements_kind() == TYPE##_ELEMENTS) { \
return roots.Type##Array_string(); \
}
TYPED_ARRAYS(SWITCH_KIND)
#undef SWITCH_KIND
}
if (IsJSPrimitiveWrapper()) {
Object value = JSPrimitiveWrapper::cast(*this).value();
if (value.IsBoolean()) return roots.Boolean_string();
if (value.IsString()) return roots.String_string();
if (value.IsNumber()) return roots.Number_string();
if (value.IsBigInt()) return roots.BigInt_string();
if (value.IsSymbol()) return roots.Symbol_string();
if (value.IsScript()) return roots.Script_string();
UNREACHABLE();
}
if (IsJSWeakMap()) return roots.WeakMap_string();
if (IsJSWeakSet()) return roots.WeakSet_string();
if (IsJSGlobalProxy()) return roots.global_string();
return roots.Object_string();
}
namespace {
std::pair<MaybeHandle<JSFunction>, Handle<String>> GetConstructorHelper(
Handle<JSReceiver> receiver) {
Isolate* isolate = receiver->GetIsolate();
// If the object was instantiated simply with base == new.target, the
// constructor on the map provides the most accurate name.
// Don't provide the info for prototypes, since their constructors are
// reclaimed and replaced by Object in OptimizeAsPrototype.
if (!receiver->IsJSProxy() && receiver->map().new_target_is_base() &&
!receiver->map().is_prototype_map()) {
Object maybe_constructor = receiver->map().GetConstructor();
if (maybe_constructor.IsJSFunction()) {
JSFunction constructor = JSFunction::cast(maybe_constructor);
String name = constructor.shared().DebugName();
if (name.length() != 0 &&
!name.Equals(ReadOnlyRoots(isolate).Object_string())) {
return std::make_pair(handle(constructor, isolate),
handle(name, isolate));
}
}
}
LookupIterator it_tag(isolate, receiver,
isolate->factory()->to_string_tag_symbol(), receiver,
LookupIterator::PROTOTYPE_CHAIN_SKIP_INTERCEPTOR);
Handle<Object> maybe_tag = JSReceiver::GetDataProperty(
&it_tag, AllocationPolicy::kAllocationDisallowed);
if (maybe_tag->IsString()) {
return std::make_pair(MaybeHandle<JSFunction>(),
Handle<String>::cast(maybe_tag));
}
PrototypeIterator iter(isolate, receiver);
if (iter.IsAtEnd()) {
return std::make_pair(MaybeHandle<JSFunction>(),
handle(receiver->class_name(), isolate));
}
Handle<JSReceiver> start = PrototypeIterator::GetCurrent<JSReceiver>(iter);
LookupIterator it(isolate, receiver, isolate->factory()->constructor_string(),
start, LookupIterator::PROTOTYPE_CHAIN_SKIP_INTERCEPTOR);
Handle<Object> maybe_constructor =
JSReceiver::GetDataProperty(&it, AllocationPolicy::kAllocationDisallowed);
if (maybe_constructor->IsJSFunction()) {
JSFunction constructor = JSFunction::cast(*maybe_constructor);
String name = constructor.shared().DebugName();
if (name.length() != 0 &&
!name.Equals(ReadOnlyRoots(isolate).Object_string())) {
return std::make_pair(handle(constructor, isolate),
handle(name, isolate));
}
}
return std::make_pair(MaybeHandle<JSFunction>(),
handle(receiver->class_name(), isolate));
}
} // anonymous namespace
// static
MaybeHandle<JSFunction> JSReceiver::GetConstructor(
Handle<JSReceiver> receiver) {
return GetConstructorHelper(receiver).first;
}
// static
Handle<String> JSReceiver::GetConstructorName(Handle<JSReceiver> receiver) {
return GetConstructorHelper(receiver).second;
}
Handle<NativeContext> JSReceiver::GetCreationContext() {
JSReceiver receiver = *this;
// Externals are JSObjects with null as a constructor.
DCHECK(!receiver.IsExternal(GetIsolate()));
Object constructor = receiver.map().GetConstructor();
JSFunction function;
if (constructor.IsJSFunction()) {
function = JSFunction::cast(constructor);
} else if (constructor.IsFunctionTemplateInfo()) {
// Remote objects don't have a creation context.
return Handle<NativeContext>::null();
} else if (receiver.IsJSGeneratorObject()) {
function = JSGeneratorObject::cast(receiver).function();
} else {
// Functions have null as a constructor,
// but any JSFunction knows its context immediately.
CHECK(receiver.IsJSFunction());
function = JSFunction::cast(receiver);
}
return function.has_context()
? Handle<NativeContext>(function.context().native_context(),
receiver.GetIsolate())
: Handle<NativeContext>::null();
}
// static
MaybeHandle<NativeContext> JSReceiver::GetFunctionRealm(
Handle<JSReceiver> receiver) {
if (receiver->IsJSProxy()) {
return JSProxy::GetFunctionRealm(Handle<JSProxy>::cast(receiver));
}
if (receiver->IsJSFunction()) {
return JSFunction::GetFunctionRealm(Handle<JSFunction>::cast(receiver));
}
if (receiver->IsJSBoundFunction()) {
return JSBoundFunction::GetFunctionRealm(
Handle<JSBoundFunction>::cast(receiver));
}
return JSObject::GetFunctionRealm(Handle<JSObject>::cast(receiver));
}
// static
MaybeHandle<NativeContext> JSReceiver::GetContextForMicrotask(
Handle<JSReceiver> receiver) {
Isolate* isolate = receiver->GetIsolate();
while (receiver->IsJSBoundFunction() || receiver->IsJSProxy()) {
if (receiver->IsJSBoundFunction()) {
receiver = handle(
Handle<JSBoundFunction>::cast(receiver)->bound_target_function(),
isolate);
} else {
DCHECK(receiver->IsJSProxy());
Handle<Object> target(Handle<JSProxy>::cast(receiver)->target(), isolate);
if (!target->IsJSReceiver()) return MaybeHandle<NativeContext>();
receiver = Handle<JSReceiver>::cast(target);
}
}
if (!receiver->IsJSFunction()) return MaybeHandle<NativeContext>();
return handle(Handle<JSFunction>::cast(receiver)->native_context(), isolate);
}
Maybe<PropertyAttributes> JSReceiver::GetPropertyAttributes(
LookupIterator* it) {
for (; it->IsFound(); it->Next()) {
switch (it->state()) {
case LookupIterator::NOT_FOUND:
case LookupIterator::TRANSITION:
UNREACHABLE();
case LookupIterator::JSPROXY:
return JSProxy::GetPropertyAttributes(it);
case LookupIterator::INTERCEPTOR: {
Maybe<PropertyAttributes> result =
JSObject::GetPropertyAttributesWithInterceptor(it);
if (result.IsNothing()) return result;
if (result.FromJust() != ABSENT) return result;
break;
}
case LookupIterator::ACCESS_CHECK:
if (it->HasAccess()) break;
return JSObject::GetPropertyAttributesWithFailedAccessCheck(it);
case LookupIterator::INTEGER_INDEXED_EXOTIC:
return Just(ABSENT);
case LookupIterator::ACCESSOR:
if (it->GetHolder<Object>()->IsJSModuleNamespace()) {
return JSModuleNamespace::GetPropertyAttributes(it);
} else {
return Just(it->property_attributes());
}
case LookupIterator::DATA:
return Just(it->property_attributes());
}
}
return Just(ABSENT);
}
namespace {
Object SetHashAndUpdateProperties(HeapObject properties, int hash) {
DCHECK_NE(PropertyArray::kNoHashSentinel, hash);
DCHECK(PropertyArray::HashField::is_valid(hash));
ReadOnlyRoots roots = properties.GetReadOnlyRoots();
if (properties == roots.empty_fixed_array() ||
properties == roots.empty_property_array() ||
properties == roots.empty_property_dictionary() ||
properties == roots.empty_ordered_property_dictionary()) {
return Smi::FromInt(hash);
}
if (properties.IsPropertyArray()) {
PropertyArray::cast(properties).SetHash(hash);
DCHECK_LT(0, PropertyArray::cast(properties).length());
return properties;
}
if (properties.IsGlobalDictionary()) {
GlobalDictionary::cast(properties).SetHash(hash);
return properties;
}
if (V8_DICT_MODE_PROTOTYPES_BOOL) {
DCHECK(properties.IsOrderedNameDictionary());
OrderedNameDictionary::cast(properties).SetHash(hash);
} else {
DCHECK(properties.IsNameDictionary());
NameDictionary::cast(properties).SetHash(hash);
}
return properties;
}
int GetIdentityHashHelper(JSReceiver object) {
DisallowHeapAllocation no_gc;
Object properties = object.raw_properties_or_hash();
if (properties.IsSmi()) {
return Smi::ToInt(properties);
}
if (properties.IsPropertyArray()) {
return PropertyArray::cast(properties).Hash();
}
if (V8_DICT_MODE_PROTOTYPES_BOOL && properties.IsOrderedNameDictionary()) {
return OrderedNameDictionary::cast(properties).Hash();
}
if (properties.IsNameDictionary()) {
DCHECK(!V8_DICT_MODE_PROTOTYPES_BOOL);
return NameDictionary::cast(properties).Hash();
}
if (properties.IsGlobalDictionary()) {
return GlobalDictionary::cast(properties).Hash();
}
#ifdef DEBUG
ReadOnlyRoots roots = object.GetReadOnlyRoots();
DCHECK(properties == roots.empty_fixed_array() ||
properties == roots.empty_property_dictionary() ||
properties == roots.empty_ordered_property_dictionary());
#endif
return PropertyArray::kNoHashSentinel;
}
} // namespace
void JSReceiver::SetIdentityHash(int hash) {
DisallowHeapAllocation no_gc;
DCHECK_NE(PropertyArray::kNoHashSentinel, hash);
DCHECK(PropertyArray::HashField::is_valid(hash));
HeapObject existing_properties = HeapObject::cast(raw_properties_or_hash());
Object new_properties = SetHashAndUpdateProperties(existing_properties, hash);
set_raw_properties_or_hash(new_properties);
}
void JSReceiver::SetProperties(HeapObject properties) {
DCHECK_IMPLIES(properties.IsPropertyArray() &&
PropertyArray::cast(properties).length() == 0,
properties == GetReadOnlyRoots().empty_property_array());
DisallowHeapAllocation no_gc;
int hash = GetIdentityHashHelper(*this);
Object new_properties = properties;
// TODO(cbruni): Make GetIdentityHashHelper return a bool so that we
// don't have to manually compare against kNoHashSentinel.
if (hash != PropertyArray::kNoHashSentinel) {
new_properties = SetHashAndUpdateProperties(properties, hash);
}
set_raw_properties_or_hash(new_properties);
}
Object JSReceiver::GetIdentityHash() {
DisallowHeapAllocation no_gc;
int hash = GetIdentityHashHelper(*this);
if (hash == PropertyArray::kNoHashSentinel) {
return GetReadOnlyRoots().undefined_value();
}
return Smi::FromInt(hash);
}
// static
Smi JSReceiver::CreateIdentityHash(Isolate* isolate, JSReceiver key) {
DisallowHeapAllocation no_gc;
int hash = isolate->GenerateIdentityHash(PropertyArray::HashField::kMax);
DCHECK_NE(PropertyArray::kNoHashSentinel, hash);
key.SetIdentityHash(hash);
return Smi::FromInt(hash);
}
Smi JSReceiver::GetOrCreateIdentityHash(Isolate* isolate) {
DisallowHeapAllocation no_gc;
int hash = GetIdentityHashHelper(*this);
if (hash != PropertyArray::kNoHashSentinel) {
return Smi::FromInt(hash);
}
return JSReceiver::CreateIdentityHash(isolate, *this);
}
void JSReceiver::DeleteNormalizedProperty(Handle<JSReceiver> object,
InternalIndex entry) {
DCHECK(!object->HasFastProperties());
Isolate* isolate = object->GetIsolate();
DCHECK(entry.is_found());
if (object->IsJSGlobalObject()) {
// If we have a global object, invalidate the cell and remove it from the
// global object's dictionary.
Handle<GlobalDictionary> dictionary(
JSGlobalObject::cast(*object).global_dictionary(), isolate);
Handle<PropertyCell> cell(dictionary->CellAt(entry), isolate);
Handle<GlobalDictionary> new_dictionary =
GlobalDictionary::DeleteEntry(isolate, dictionary, entry);
JSGlobalObject::cast(*object).set_global_dictionary(*new_dictionary);
cell->ClearAndInvalidate(ReadOnlyRoots(isolate));
} else {
if (V8_DICT_MODE_PROTOTYPES_BOOL) {
Handle<OrderedNameDictionary> dictionary(
object->property_dictionary_ordered(), isolate);
dictionary =
OrderedNameDictionary::DeleteEntry(isolate, dictionary, entry);
object->SetProperties(*dictionary);
} else {
Handle<NameDictionary> dictionary(object->property_dictionary(), isolate);
dictionary = NameDictionary::DeleteEntry(isolate, dictionary, entry);
object->SetProperties(*dictionary);
}
}
if (object->map().is_prototype_map()) {
// Invalidate prototype validity cell as this may invalidate transitioning
// store IC handlers.
JSObject::InvalidatePrototypeChains(object->map());
}
}
Maybe<bool> JSReceiver::DeleteProperty(LookupIterator* it,
LanguageMode language_mode) {
it->UpdateProtector();
Isolate* isolate = it->isolate();
if (it->state() == LookupIterator::JSPROXY) {
return JSProxy::DeletePropertyOrElement(it->GetHolder<JSProxy>(),
it->GetName(), language_mode);
}
if (it->GetReceiver()->IsJSProxy()) {
if (it->state() != LookupIterator::NOT_FOUND) {
DCHECK_EQ(LookupIterator::DATA, it->state());
DCHECK(it->name()->IsPrivate());
it->Delete();
}
return Just(true);
}
Handle<JSObject> receiver = Handle<JSObject>::cast(it->GetReceiver());
for (; it->IsFound(); it->Next()) {
switch (it->state()) {
case LookupIterator::JSPROXY:
case LookupIterator::NOT_FOUND:
case LookupIterator::TRANSITION:
UNREACHABLE();
case LookupIterator::ACCESS_CHECK:
if (it->HasAccess()) break;
isolate->ReportFailedAccessCheck(it->GetHolder<JSObject>());
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, Nothing<bool>());
return Just(false);
case LookupIterator::INTERCEPTOR: {
ShouldThrow should_throw =
is_sloppy(language_mode) ? kDontThrow : kThrowOnError;
Maybe<bool> result =
JSObject::DeletePropertyWithInterceptor(it, should_throw);
// An exception was thrown in the interceptor. Propagate.
if (isolate->has_pending_exception()) return Nothing<bool>();
// Delete with interceptor succeeded. Return result.
// TODO(neis): In strict mode, we should probably throw if the
// interceptor returns false.
if (result.IsJust()) return result;
break;
}
case LookupIterator::INTEGER_INDEXED_EXOTIC:
return Just(true);
case LookupIterator::DATA:
case LookupIterator::ACCESSOR: {
if (!it->IsConfigurable()) {
// Fail if the property is not configurable.
if (is_strict(language_mode)) {
isolate->Throw(*isolate->factory()->NewTypeError(
MessageTemplate::kStrictDeleteProperty, it->GetName(),
receiver));
return Nothing<bool>();
}
return Just(false);
}
it->Delete();
return Just(true);
}
}
}
return Just(true);
}
Maybe<bool> JSReceiver::DeleteElement(Handle<JSReceiver> object, uint32_t index,
LanguageMode language_mode) {
LookupIterator it(object->GetIsolate(), object, index, object,
LookupIterator::OWN);
return DeleteProperty(&it, language_mode);
}
Maybe<bool> JSReceiver::DeleteProperty(Handle<JSReceiver> object,
Handle<Name> name,
LanguageMode language_mode) {
LookupIterator it(object->GetIsolate(), object, name, object,
LookupIterator::OWN);
return DeleteProperty(&it, language_mode);
}
Maybe<bool> JSReceiver::DeletePropertyOrElement(Handle<JSReceiver> object,
Handle<Name> name,
LanguageMode language_mode) {
Isolate* isolate = object->GetIsolate();
LookupIterator::Key key(isolate, name);
LookupIterator it(isolate, object, key, object, LookupIterator::OWN);
return DeleteProperty(&it, language_mode);
}
// ES6 19.1.2.4
// static
Object JSReceiver::DefineProperty(Isolate* isolate, Handle<Object> object,
Handle<Object> key,
Handle<Object> attributes) {
// 1. If Type(O) is not Object, throw a TypeError exception.
if (!object->IsJSReceiver()) {
Handle<String> fun_name =
isolate->factory()->InternalizeUtf8String("Object.defineProperty");
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kCalledOnNonObject, fun_name));
}
// 2. Let key be ToPropertyKey(P).
// 3. ReturnIfAbrupt(key).
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, key,
Object::ToPropertyKey(isolate, key));
// 4. Let desc be ToPropertyDescriptor(Attributes).
// 5. ReturnIfAbrupt(desc).
PropertyDescriptor desc;
if (!PropertyDescriptor::ToPropertyDescriptor(isolate, attributes, &desc)) {
return ReadOnlyRoots(isolate).exception();
}
// 6. Let success be DefinePropertyOrThrow(O,key, desc).
Maybe<bool> success =
DefineOwnProperty(isolate, Handle<JSReceiver>::cast(object), key, &desc,
Just(kThrowOnError));
// 7. ReturnIfAbrupt(success).
MAYBE_RETURN(success, ReadOnlyRoots(isolate).exception());
CHECK(success.FromJust());
// 8. Return O.
return *object;
}
// ES6 19.1.2.3.1
// static
MaybeHandle<Object> JSReceiver::DefineProperties(Isolate* isolate,
Handle<Object> object,
Handle<Object> properties) {
// 1. If Type(O) is not Object, throw a TypeError exception.
if (!object->IsJSReceiver()) {
Handle<String> fun_name =
isolate->factory()->InternalizeUtf8String("Object.defineProperties");
THROW_NEW_ERROR(isolate,
NewTypeError(MessageTemplate::kCalledOnNonObject, fun_name),
Object);
}
// 2. Let props be ToObject(Properties).
// 3. ReturnIfAbrupt(props).
Handle<JSReceiver> props;
ASSIGN_RETURN_ON_EXCEPTION(isolate, props,
Object::ToObject(isolate, properties), Object);
// 4. Let keys be props.[[OwnPropertyKeys]]().
// 5. ReturnIfAbrupt(keys).
Handle<FixedArray> keys;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, keys,
KeyAccumulator::GetKeys(props, KeyCollectionMode::kOwnOnly,
ALL_PROPERTIES),
Object);
// 6. Let descriptors be an empty List.
int capacity = keys->length();
std::vector<PropertyDescriptor> descriptors(capacity);
size_t descriptors_index = 0;
// 7. Repeat for each element nextKey of keys in List order,
for (int i = 0; i < keys->length(); ++i) {
Handle<Object> next_key(keys->get(i), isolate);
// 7a. Let propDesc be props.[[GetOwnProperty]](nextKey).
// 7b. ReturnIfAbrupt(propDesc).
LookupIterator::Key key(isolate, next_key);
LookupIterator it(isolate, props, key, LookupIterator::OWN);
Maybe<PropertyAttributes> maybe = JSReceiver::GetPropertyAttributes(&it);
if (maybe.IsNothing()) return MaybeHandle<Object>();
PropertyAttributes attrs = maybe.FromJust();
// 7c. If propDesc is not undefined and propDesc.[[Enumerable]] is true:
if (attrs == ABSENT) continue;
if (attrs & DONT_ENUM) continue;
// 7c i. Let descObj be Get(props, nextKey).
// 7c ii. ReturnIfAbrupt(descObj).
Handle<Object> desc_obj;
ASSIGN_RETURN_ON_EXCEPTION(isolate, desc_obj, Object::GetProperty(&it),
Object);
// 7c iii. Let desc be ToPropertyDescriptor(descObj).
bool success = PropertyDescriptor::ToPropertyDescriptor(
isolate, desc_obj, &descriptors[descriptors_index]);
// 7c iv. ReturnIfAbrupt(desc).
if (!success) return MaybeHandle<Object>();
// 7c v. Append the pair (a two element List) consisting of nextKey and
// desc to the end of descriptors.
descriptors[descriptors_index].set_name(next_key);
descriptors_index++;
}
// 8. For each pair from descriptors in list order,
for (size_t i = 0; i < descriptors_index; ++i) {
PropertyDescriptor* desc = &descriptors[i];
// 8a. Let P be the first element of pair.
// 8b. Let desc be the second element of pair.
// 8c. Let status be DefinePropertyOrThrow(O, P, desc).
Maybe<bool> status =
DefineOwnProperty(isolate, Handle<JSReceiver>::cast(object),
desc->name(), desc, Just(kThrowOnError));
// 8d. ReturnIfAbrupt(status).
if (status.IsNothing()) return MaybeHandle<Object>();
CHECK(status.FromJust());
}
// 9. Return o.
return object;
}
// static
Maybe<bool> JSReceiver::DefineOwnProperty(Isolate* isolate,
Handle<JSReceiver> object,
Handle<Object> key,
PropertyDescriptor* desc,
Maybe<ShouldThrow> should_throw) {
if (object->IsJSArray()) {
return JSArray::DefineOwnProperty(isolate, Handle<JSArray>::cast(object),
key, desc, should_throw);
}
if (object->IsJSProxy()) {
return JSProxy::DefineOwnProperty(isolate, Handle<JSProxy>::cast(object),
key, desc, should_throw);
}
if (object->IsJSTypedArray()) {
return JSTypedArray::DefineOwnProperty(
isolate, Handle<JSTypedArray>::cast(object), key, desc, should_throw);
}
// OrdinaryDefineOwnProperty, by virtue of calling
// DefineOwnPropertyIgnoreAttributes, can handle arguments
// (ES#sec-arguments-exotic-objects-defineownproperty-p-desc).
return OrdinaryDefineOwnProperty(isolate, Handle<JSObject>::cast(object), key,
desc, should_throw);
}
// static
Maybe<bool> JSReceiver::OrdinaryDefineOwnProperty(
Isolate* isolate, Handle<JSObject> object, Handle<Object> key,
PropertyDescriptor* desc, Maybe<ShouldThrow> should_throw) {
DCHECK(key->IsName() || key->IsNumber()); // |key| is a PropertyKey.
LookupIterator::Key lookup_key(isolate, key);
LookupIterator it(isolate, object, lookup_key, LookupIterator::OWN);
// Deal with access checks first.
if (it.state() == LookupIterator::ACCESS_CHECK) {
if (!it.HasAccess()) {
isolate->ReportFailedAccessCheck(it.GetHolder<JSObject>());
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, Nothing<bool>());
return Just(true);
}
it.Next();
}
return OrdinaryDefineOwnProperty(&it, desc, should_throw);
}
namespace {
MaybeHandle<Object> GetPropertyWithInterceptorInternal(
LookupIterator* it, Handle<InterceptorInfo> interceptor, bool* done) {
*done = false;
Isolate* isolate = it->isolate();
// Make sure that the top context does not change when doing callbacks or
// interceptor calls.
AssertNoContextChange ncc(isolate);
if (interceptor->getter().IsUndefined(isolate)) {
return isolate->factory()->undefined_value();
}
Handle<JSObject> holder = it->GetHolder<JSObject>();
Handle<Object> result;
Handle<Object> receiver = it->GetReceiver();
if (!receiver->IsJSReceiver()) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, receiver, Object::ConvertReceiver(isolate, receiver), Object);
}
PropertyCallbackArguments args(isolate, interceptor->data(), *receiver,
*holder, Just(kDontThrow));
if (it->IsElement(*holder)) {
result = args.CallIndexedGetter(interceptor, it->array_index());
} else {
result = args.CallNamedGetter(interceptor, it->name());
}
RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION(isolate, Object);
if (result.is_null()) return isolate->factory()->undefined_value();
*done = true;
// Rebox handle before return
return handle(*result, isolate);
}
Maybe<PropertyAttributes> GetPropertyAttributesWithInterceptorInternal(
LookupIterator* it, Handle<InterceptorInfo> interceptor) {
Isolate* isolate = it->isolate();
// Make sure that the top context does not change when doing
// callbacks or interceptor calls.
AssertNoContextChange ncc(isolate);
HandleScope scope(isolate);
Handle<JSObject> holder = it->GetHolder<JSObject>();
DCHECK_IMPLIES(!it->IsElement(*holder) && it->name()->IsSymbol(),
interceptor->can_intercept_symbols());
Handle<Object> receiver = it->GetReceiver();
if (!receiver->IsJSReceiver()) {
ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, receiver,
Object::ConvertReceiver(isolate, receiver),
Nothing<PropertyAttributes>());
}
PropertyCallbackArguments args(isolate, interceptor->data(), *receiver,
*holder, Just(kDontThrow));
if (!interceptor->query().IsUndefined(isolate)) {
Handle<Object> result;
if (it->IsElement(*holder)) {
result = args.CallIndexedQuery(interceptor, it->array_index());
} else {
result = args.CallNamedQuery(interceptor, it->name());
}
if (!result.is_null()) {
int32_t value;
CHECK(result->ToInt32(&value));
return Just(static_cast<PropertyAttributes>(value));
}
} else if (!interceptor->getter().IsUndefined(isolate)) {
// TODO(verwaest): Use GetPropertyWithInterceptor?
Handle<Object> result;
if (it->IsElement(*holder)) {
result = args.CallIndexedGetter(interceptor, it->array_index());
} else {
result = args.CallNamedGetter(interceptor, it->name());
}
if (!result.is_null()) return Just(DONT_ENUM);
}
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, Nothing<PropertyAttributes>());
return Just(ABSENT);
}
Maybe<bool> SetPropertyWithInterceptorInternal(
LookupIterator* it, Handle<InterceptorInfo> interceptor,
Maybe<ShouldThrow> should_throw, Handle<Object> value) {
Isolate* isolate = it->isolate();
// Make sure that the top context does not change when doing callbacks or
// interceptor calls.
AssertNoContextChange ncc(isolate);
if (interceptor->setter().IsUndefined(isolate)) return Just(false);
Handle<JSObject> holder = it->GetHolder<JSObject>();
bool result;
Handle<Object> receiver = it->GetReceiver();
if (!receiver->IsJSReceiver()) {
ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, receiver,
Object::ConvertReceiver(isolate, receiver),
Nothing<bool>());
}
PropertyCallbackArguments args(isolate, interceptor->data(), *receiver,
*holder, should_throw);
if (it->IsElement(*holder)) {
// TODO(neis): In the future, we may want to actually return the
// interceptor's result, which then should be a boolean.
result = !args.CallIndexedSetter(interceptor, it->array_index(), value)
.is_null();
} else {
result = !args.CallNamedSetter(interceptor, it->name(), value).is_null();
}
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(it->isolate(), Nothing<bool>());
return Just(result);
}
Maybe<bool> DefinePropertyWithInterceptorInternal(
LookupIterator* it, Handle<InterceptorInfo> interceptor,
Maybe<ShouldThrow> should_throw, PropertyDescriptor* desc) {
Isolate* isolate = it->isolate();
// Make sure that the top context does not change when doing callbacks or
// interceptor calls.
AssertNoContextChange ncc(isolate);
if (interceptor->definer().IsUndefined(isolate)) return Just(false);
Handle<JSObject> holder = it->GetHolder<JSObject>();
bool result;
Handle<Object> receiver = it->GetReceiver();
if (!receiver->IsJSReceiver()) {
ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, receiver,
Object::ConvertReceiver(isolate, receiver),
Nothing<bool>());
}
PropertyCallbackArguments args(isolate, interceptor->data(), *receiver,
*holder, should_throw);
std::unique_ptr<v8::PropertyDescriptor> descriptor(
new v8::PropertyDescriptor());
if (PropertyDescriptor::IsAccessorDescriptor(desc)) {
descriptor.reset(new v8::PropertyDescriptor(
v8::Utils::ToLocal(desc->get()), v8::Utils::ToLocal(desc->set())));
} else if (PropertyDescriptor::IsDataDescriptor(desc)) {
if (desc->has_writable()) {
descriptor.reset(new v8::PropertyDescriptor(
v8::Utils::ToLocal(desc->value()), desc->writable()));
} else {
descriptor.reset(
new v8::PropertyDescriptor(v8::Utils::ToLocal(desc->value())));
}
}
if (desc->has_enumerable()) {
descriptor->set_enumerable(desc->enumerable());
}
if (desc->has_configurable()) {
descriptor->set_configurable(desc->configurable());
}
if (it->IsElement(*holder)) {
result =
!args.CallIndexedDefiner(interceptor, it->array_index(), *descriptor)
.is_null();
} else {
result =
!args.CallNamedDefiner(interceptor, it->name(), *descriptor).is_null();
}
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(it->isolate(), Nothing<bool>());
return Just(result);
}
} // namespace
// ES6 9.1.6.1
// static
Maybe<bool> JSReceiver::OrdinaryDefineOwnProperty(
LookupIterator* it, PropertyDescriptor* desc,
Maybe<ShouldThrow> should_throw) {
Isolate* isolate = it->isolate();
// 1. Let current be O.[[GetOwnProperty]](P).
// 2. ReturnIfAbrupt(current).
PropertyDescriptor current;
MAYBE_RETURN(GetOwnPropertyDescriptor(it, &current), Nothing<bool>());
it->Restart();
// Handle interceptor
for (; it->IsFound(); it->Next()) {
if (it->state() == LookupIterator::INTERCEPTOR) {
if (it->HolderIsReceiverOrHiddenPrototype()) {
Maybe<bool> result = DefinePropertyWithInterceptorInternal(
it, it->GetInterceptor(), should_throw, desc);
if (result.IsNothing() || result.FromJust()) {
return result;
}
}
}
}
// TODO(jkummerow/verwaest): It would be nice if we didn't have to reset
// the iterator every time. Currently, the reasons why we need it are:
// - handle interceptors correctly
// - handle accessors correctly (which might change the holder's map)
it->Restart();
// 3. Let extensible be the value of the [[Extensible]] internal slot of O.
Handle<JSObject> object = Handle<JSObject>::cast(it->GetReceiver());
bool extensible = JSObject::IsExtensible(object);
return ValidateAndApplyPropertyDescriptor(
isolate, it, extensible, desc, &current, should_throw, Handle<Name>());
}
// ES6 9.1.6.2
// static
Maybe<bool> JSReceiver::IsCompatiblePropertyDescriptor(
Isolate* isolate, bool extensible, PropertyDescriptor* desc,
PropertyDescriptor* current, Handle<Name> property_name,
Maybe<ShouldThrow> should_throw) {
// 1. Return ValidateAndApplyPropertyDescriptor(undefined, undefined,
// Extensible, Desc, Current).
return ValidateAndApplyPropertyDescriptor(
isolate, nullptr, extensible, desc, current, should_throw, property_name);
}
// ES6 9.1.6.3
// static
Maybe<bool> JSReceiver::ValidateAndApplyPropertyDescriptor(
Isolate* isolate, LookupIterator* it, bool extensible,
PropertyDescriptor* desc, PropertyDescriptor* current,
Maybe<ShouldThrow> should_throw, Handle<Name> property_name) {
// We either need a LookupIterator, or a property name.
DCHECK((it == nullptr) != property_name.is_null());
Handle<JSObject> object;
if (it != nullptr) object = Handle<JSObject>::cast(it->GetReceiver());
bool desc_is_data_descriptor = PropertyDescriptor::IsDataDescriptor(desc);
bool desc_is_accessor_descriptor =
PropertyDescriptor::IsAccessorDescriptor(desc);
bool desc_is_generic_descriptor =
PropertyDescriptor::IsGenericDescriptor(desc);
// 1. (Assert)
// 2. If current is undefined, then
if (current->is_empty()) {
// 2a. If extensible is false, return false.
if (!extensible) {
RETURN_FAILURE(
isolate, GetShouldThrow(isolate, should_throw),
NewTypeError(MessageTemplate::kDefineDisallowed,
it != nullptr ? it->GetName() : property_name));
}
// 2c. If IsGenericDescriptor(Desc) or IsDataDescriptor(Desc) is true, then:
// (This is equivalent to !IsAccessorDescriptor(desc).)
DCHECK((desc_is_generic_descriptor || desc_is_data_descriptor) ==
!desc_is_accessor_descriptor);
if (!desc_is_accessor_descriptor) {
// 2c i. If O is not undefined, create an own data property named P of
// object O whose [[Value]], [[Writable]], [[Enumerable]] and
// [[Configurable]] attribute values are described by Desc. If the value
// of an attribute field of Desc is absent, the attribute of the newly
// created property is set to its default value.
if (it != nullptr) {
if (!desc->has_writable()) desc->set_writable(false);
if (!desc->has_enumerable()) desc->set_enumerable(false);
if (!desc->has_configurable()) desc->set_configurable(false);
Handle<Object> value(
desc->has_value()
? desc->value()
: Handle<Object>::cast(isolate->factory()->undefined_value()));
MaybeHandle<Object> result =
JSObject::DefineOwnPropertyIgnoreAttributes(it, value,
desc->ToAttributes());
if (result.is_null()) return Nothing<bool>();
}
} else {
// 2d. Else Desc must be an accessor Property Descriptor,
DCHECK(desc_is_accessor_descriptor);
// 2d i. If O is not undefined, create an own accessor property named P
// of object O whose [[Get]], [[Set]], [[Enumerable]] and
// [[Configurable]] attribute values are described by Desc. If the value
// of an attribute field of Desc is absent, the attribute of the newly
// created property is set to its default value.
if (it != nullptr) {
if (!desc->has_enumerable()) desc->set_enumerable(false);
if (!desc->has_configurable()) desc->set_configurable(false);
Handle<Object> getter(
desc->has_get()
? desc->get()
: Handle<Object>::cast(isolate->factory()->null_value()));
Handle<Object> setter(
desc->has_set()
? desc->set()
: Handle<Object>::cast(isolate->factory()->null_value()));
MaybeHandle<Object> result =
JSObject::DefineAccessor(it, getter, setter, desc->ToAttributes());
if (result.is_null()) return Nothing<bool>();
}
}
// 2e. Return true.
return Just(true);
}
// 3. Return true, if every field in Desc is absent.
// 4. Return true, if every field in Desc also occurs in current and the
// value of every field in Desc is the same value as the corresponding field
// in current when compared using the SameValue algorithm.
if ((!desc->has_enumerable() ||
desc->enumerable() == current->enumerable()) &&
(!desc->has_configurable() ||
desc->configurable() == current->configurable()) &&
(!desc->has_value() ||
(current->has_value() && current->value()->SameValue(*desc->value()))) &&
(!desc->has_writable() ||
(current->has_writable() && current->writable() == desc->writable())) &&
(!desc->has_get() ||
(current->has_get() && current->get()->SameValue(*desc->get()))) &&
(!desc->has_set() ||
(current->has_set() && current->set()->SameValue(*desc->set())))) {
return Just(true);
}
// 5. If the [[Configurable]] field of current is false, then
if (!current->configurable()) {
// 5a. Return false, if the [[Configurable]] field of Desc is true.
if (desc->has_configurable() && desc->configurable()) {
RETURN_FAILURE(
isolate, GetShouldThrow(isolate, should_throw),
NewTypeError(MessageTemplate::kRedefineDisallowed,
it != nullptr ? it->GetName() : property_name));
}
// 5b. Return false, if the [[Enumerable]] field of Desc is present and the
// [[Enumerable]] fields of current and Desc are the Boolean negation of
// each other.
if (desc->has_enumerable() && desc->enumerable() != current->enumerable()) {
RETURN_FAILURE(
isolate, GetShouldThrow(isolate, should_throw),
NewTypeError(MessageTemplate::kRedefineDisallowed,
it != nullptr ? it->GetName() : property_name));
}
}
bool current_is_data_descriptor =
PropertyDescriptor::IsDataDescriptor(current);
// 6. If IsGenericDescriptor(Desc) is true, no further validation is required.
if (desc_is_generic_descriptor) {
// Nothing to see here.
// 7. Else if IsDataDescriptor(current) and IsDataDescriptor(Desc) have
// different results, then:
} else if (current_is_data_descriptor != desc_is_data_descriptor) {
// 7a. Return false, if the [[Configurable]] field of current is false.
if (!current->configurable()) {
RETURN_FAILURE(
isolate, GetShouldThrow(isolate, should_throw),
NewTypeError(MessageTemplate::kRedefineDisallowed,
it != nullptr ? it->GetName() : property_name));
}
// 7b. If IsDataDescriptor(current) is true, then:
if (current_is_data_descriptor) {
// 7b i. If O is not undefined, convert the property named P of object O
// from a data property to an accessor property. Preserve the existing
// values of the converted property's [[Configurable]] and [[Enumerable]]
// attributes and set the rest of the property's attributes to their
// default values.
// --> Folded into step 10.
} else {
// 7c i. If O is not undefined, convert the property named P of object O
// from an accessor property to a data property. Preserve the existing
// values of the converted property’s [[Configurable]] and [[Enumerable]]
// attributes and set the rest of the property’s attributes to their
// default values.
// --> Folded into step 10.
}
// 8. Else if IsDataDescriptor(current) and IsDataDescriptor(Desc) are both
// true, then:
} else if (current_is_data_descriptor && desc_is_data_descriptor) {
// 8a. If the [[Configurable]] field of current is false, then:
if (!current->configurable()) {
// 8a i. Return false, if the [[Writable]] field of current is false and
// the [[Writable]] field of Desc is true.
if (!current->writable() && desc->has_writable() && desc->writable()) {
RETURN_FAILURE(
isolate, GetShouldThrow(isolate, should_throw),
NewTypeError(MessageTemplate::kRedefineDisallowed,
it != nullptr ? it->GetName() : property_name));
}
// 8a ii. If the [[Writable]] field of current is false, then:
if (!current->writable()) {
// 8a ii 1. Return false, if the [[Value]] field of Desc is present and
// SameValue(Desc.[[Value]], current.[[Value]]) is false.
if (desc->has_value() && !desc->value()->SameValue(*current->value())) {
RETURN_FAILURE(
isolate, GetShouldThrow(isolate, should_throw),
NewTypeError(MessageTemplate::kRedefineDisallowed,
it != nullptr ? it->GetName() : property_name));
}
}
}
} else {
// 9. Else IsAccessorDescriptor(current) and IsAccessorDescriptor(Desc)
// are both true,
DCHECK(PropertyDescriptor::IsAccessorDescriptor(current) &&
desc_is_accessor_descriptor);
// 9a. If the [[Configurable]] field of current is false, then:
if (!current->configurable()) {
// 9a i. Return false, if the [[Set]] field of Desc is present and
// SameValue(Desc.[[Set]], current.[[Set]]) is false.
if (desc->has_set() && !desc->set()->SameValue(*current->set())) {
RETURN_FAILURE(
isolate, GetShouldThrow(isolate, should_throw),
NewTypeError(MessageTemplate::kRedefineDisallowed,
it != nullptr ? it->GetName() : property_name));
}
// 9a ii. Return false, if the [[Get]] field of Desc is present and
// SameValue(Desc.[[Get]], current.[[Get]]) is false.
if (desc->has_get() && !desc->get()->SameValue(*current->get())) {
RETURN_FAILURE(
isolate, GetShouldThrow(isolate, should_throw),
NewTypeError(MessageTemplate::kRedefineDisallowed,
it != nullptr ? it->GetName() : property_name));
}
}
}
// 10. If O is not undefined, then:
if (it != nullptr) {
// 10a. For each field of Desc that is present, set the corresponding
// attribute of the property named P of object O to the value of the field.
PropertyAttributes attrs = NONE;
if (desc->has_enumerable()) {
attrs = static_cast<PropertyAttributes>(
attrs | (desc->enumerable() ? NONE : DONT_ENUM));
} else {
attrs = static_cast<PropertyAttributes>(
attrs | (current->enumerable() ? NONE : DONT_ENUM));
}
if (desc->has_configurable()) {
attrs = static_cast<PropertyAttributes>(
attrs | (desc->configurable() ? NONE : DONT_DELETE));
} else {
attrs = static_cast<PropertyAttributes>(
attrs | (current->configurable() ? NONE : DONT_DELETE));
}
if (desc_is_data_descriptor ||
(desc_is_generic_descriptor && current_is_data_descriptor)) {
if (desc->has_writable()) {
attrs = static_cast<PropertyAttributes>(
attrs | (desc->writable() ? NONE : READ_ONLY));
} else {
attrs = static_cast<PropertyAttributes>(
attrs | (current->writable() ? NONE : READ_ONLY));
}
Handle<Object> value(
desc->has_value() ? desc->value()
: current->has_value()
? current->value()
: Handle<Object>::cast(
isolate->factory()->undefined_value()));
return JSObject::DefineOwnPropertyIgnoreAttributes(it, value, attrs,
should_throw);
} else {
DCHECK(desc_is_accessor_descriptor ||
(desc_is_generic_descriptor &&
PropertyDescriptor::IsAccessorDescriptor(current)));
Handle<Object> getter(
desc->has_get()
? desc->get()
: current->has_get()
? current->get()
: Handle<Object>::cast(isolate->factory()->null_value()));
Handle<Object> setter(
desc->has_set()
? desc->set()
: current->has_set()
? current->set()
: Handle<Object>::cast(isolate->factory()->null_value()));
MaybeHandle<Object> result =
JSObject::DefineAccessor(it, getter, setter, attrs);
if (result.is_null()) return Nothing<bool>();
}
}
// 11. Return true.
return Just(true);
}
// static
Maybe<bool> JSReceiver::CreateDataProperty(Isolate* isolate,
Handle<JSReceiver> object,
Handle<Name> key,
Handle<Object> value,
Maybe<ShouldThrow> should_throw) {
LookupIterator::Key lookup_key(isolate, key);
LookupIterator it(isolate, object, lookup_key, LookupIterator::OWN);
return CreateDataProperty(&it, value, should_throw);
}
// static
Maybe<bool> JSReceiver::CreateDataProperty(LookupIterator* it,
Handle<Object> value,
Maybe<ShouldThrow> should_throw) {
DCHECK(!it->check_prototype_chain());
Handle<JSReceiver> receiver = Handle<JSReceiver>::cast(it->GetReceiver());
Isolate* isolate = receiver->GetIsolate();
if (receiver->IsJSObject()) {
return JSObject::CreateDataProperty(it, value, should_throw); // Shortcut.
}
PropertyDescriptor new_desc;
new_desc.set_value(value);
new_desc.set_writable(true);
new_desc.set_enumerable(true);
new_desc.set_configurable(true);
return JSReceiver::DefineOwnProperty(isolate, receiver, it->GetName(),
&new_desc, should_throw);
}
// static
Maybe<bool> JSReceiver::GetOwnPropertyDescriptor(Isolate* isolate,
Handle<JSReceiver> object,
Handle<Object> key,
PropertyDescriptor* desc) {
DCHECK(key->IsName() || key->IsNumber()); // |key| is a PropertyKey.
LookupIterator::Key lookup_key(isolate, key);
LookupIterator it(isolate, object, lookup_key, LookupIterator::OWN);
return GetOwnPropertyDescriptor(&it, desc);
}
namespace {
Maybe<bool> GetPropertyDescriptorWithInterceptor(LookupIterator* it,
PropertyDescriptor* desc) {
Handle<InterceptorInfo> interceptor;
if (it->state() == LookupIterator::ACCESS_CHECK) {
if (it->HasAccess()) {
it->Next();
} else {
interceptor = it->GetInterceptorForFailedAccessCheck();
if (interceptor.is_null() &&
(!JSObject::AllCanRead(it) ||
it->state() != LookupIterator::INTERCEPTOR)) {
it->Restart();
return Just(false);
}
}
}
if (it->state() == LookupIterator::INTERCEPTOR) {
interceptor = it->GetInterceptor();
}
if (interceptor.is_null()) return Just(false);
Isolate* isolate = it->isolate();
if (interceptor->descriptor().IsUndefined(isolate)) return Just(false);
Handle<Object> result;
Handle<JSObject> holder = it->GetHolder<JSObject>();
Handle<Object> receiver = it->GetReceiver();
if (!receiver->IsJSReceiver()) {
ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, receiver,
Object::ConvertReceiver(isolate, receiver),
Nothing<bool>());
}
PropertyCallbackArguments args(isolate, interceptor->data(), *receiver,
*holder, Just(kDontThrow));
if (it->IsElement(*holder)) {
result = args.CallIndexedDescriptor(interceptor, it->array_index());
} else {
result = args.CallNamedDescriptor(interceptor, it->name());
}
// An exception was thrown in the interceptor. Propagate.
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, Nothing<bool>());
if (!result.is_null()) {
// Request successfully intercepted, try to set the property
// descriptor.
Utils::ApiCheck(
PropertyDescriptor::ToPropertyDescriptor(isolate, result, desc),
it->IsElement(*holder) ? "v8::IndexedPropertyDescriptorCallback"
: "v8::NamedPropertyDescriptorCallback",
"Invalid property descriptor.");
return Just(true);
}
it->Next();
return Just(false);
}
} // namespace
// ES6 9.1.5.1
// Returns true on success, false if the property didn't exist, nothing if
// an exception was thrown.
// static
Maybe<bool> JSReceiver::GetOwnPropertyDescriptor(LookupIterator* it,
PropertyDescriptor* desc) {
Isolate* isolate = it->isolate();
// "Virtual" dispatch.
if (it->IsFound() && it->GetHolder<JSReceiver>()->IsJSProxy()) {
return JSProxy::GetOwnPropertyDescriptor(isolate, it->GetHolder<JSProxy>(),
it->GetName(), desc);
}
Maybe<bool> intercepted = GetPropertyDescriptorWithInterceptor(it, desc);
MAYBE_RETURN(intercepted, Nothing<bool>());
if (intercepted.FromJust()) {
return Just(true);
}
// Request was not intercepted, continue as normal.
// 1. (Assert)
// 2. If O does not have an own property with key P, return undefined.
Maybe<PropertyAttributes> maybe = JSObject::GetPropertyAttributes(it);
MAYBE_RETURN(maybe, Nothing<bool>());
PropertyAttributes attrs = maybe.FromJust();
if (attrs == ABSENT) return Just(false);
DCHECK(!isolate->has_pending_exception());
// 3. Let D be a newly created Property Descriptor with no fields.
DCHECK(desc->is_empty());
// 4. Let X be O's own property whose key is P.
// 5. If X is a data property, then
bool is_accessor_pair = it->state() == LookupIterator::ACCESSOR &&
it->GetAccessors()->IsAccessorPair();
if (!is_accessor_pair) {
// 5a. Set D.[[Value]] to the value of X's [[Value]] attribute.
Handle<Object> value;
if (!Object::GetProperty(it).ToHandle(&value)) {
DCHECK(isolate->has_pending_exception());
return Nothing<bool>();
}
desc->set_value(value);
// 5b. Set D.[[Writable]] to the value of X's [[Writable]] attribute
desc->set_writable((attrs & READ_ONLY) == 0);
} else {
// 6. Else X is an accessor property, so
Handle<AccessorPair> accessors =
Handle<AccessorPair>::cast(it->GetAccessors());
Handle<NativeContext> native_context =
it->GetHolder<JSReceiver>()->GetCreationContext();
// 6a. Set D.[[Get]] to the value of X's [[Get]] attribute.
desc->set_get(AccessorPair::GetComponent(isolate, native_context, accessors,
ACCESSOR_GETTER));
// 6b. Set D.[[Set]] to the value of X's [[Set]] attribute.
desc->set_set(AccessorPair::GetComponent(isolate, native_context, accessors,
ACCESSOR_SETTER));
}
// 7. Set D.[[Enumerable]] to the value of X's [[Enumerable]] attribute.
desc->set_enumerable((attrs & DONT_ENUM) == 0);
// 8. Set D.[[Configurable]] to the value of X's [[Configurable]] attribute.
desc->set_configurable((attrs & DONT_DELETE) == 0);
// 9. Return D.
DCHECK(PropertyDescriptor::IsAccessorDescriptor(desc) !=
PropertyDescriptor::IsDataDescriptor(desc));
return Just(true);
}
Maybe<bool> JSReceiver::SetIntegrityLevel(Handle<JSReceiver> receiver,
IntegrityLevel level,
ShouldThrow should_throw) {
DCHECK(level == SEALED || level == FROZEN);
if (receiver->IsJSObject()) {
Handle<JSObject> object = Handle<JSObject>::cast(receiver);
if (!object->HasSloppyArgumentsElements() &&
!object->IsJSModuleNamespace()) { // Fast path.
// Prevent memory leaks by not adding unnecessary transitions.
Maybe<bool> test = JSObject::TestIntegrityLevel(object, level);
MAYBE_RETURN(test, Nothing<bool>());
if (test.FromJust()) return test;
if (level == SEALED) {
return JSObject::PreventExtensionsWithTransition<SEALED>(object,
should_throw);
} else {
return JSObject::PreventExtensionsWithTransition<FROZEN>(object,
should_throw);
}
}
}
Isolate* isolate = receiver->GetIsolate();
MAYBE_RETURN(JSReceiver::PreventExtensions(receiver, should_throw),
Nothing<bool>());
Handle<FixedArray> keys;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, keys, JSReceiver::OwnPropertyKeys(receiver), Nothing<bool>());
PropertyDescriptor no_conf;
no_conf.set_configurable(false);
PropertyDescriptor no_conf_no_write;
no_conf_no_write.set_configurable(false);
no_conf_no_write.set_writable(false);
if (level == SEALED) {
for (int i = 0; i < keys->length(); ++i) {
Handle<Object> key(keys->get(i), isolate);
MAYBE_RETURN(DefineOwnProperty(isolate, receiver, key, &no_conf,
Just(kThrowOnError)),
Nothing<bool>());
}
return Just(true);
}
for (int i = 0; i < keys->length(); ++i) {
Handle<Object> key(keys->get(i), isolate);
PropertyDescriptor current_desc;
Maybe<bool> owned = JSReceiver::GetOwnPropertyDescriptor(
isolate, receiver, key, &current_desc);
MAYBE_RETURN(owned, Nothing<bool>());
if (owned.FromJust()) {
PropertyDescriptor desc =
PropertyDescriptor::IsAccessorDescriptor(&current_desc)
? no_conf
: no_conf_no_write;
MAYBE_RETURN(
DefineOwnProperty(isolate, receiver, key, &desc, Just(kThrowOnError)),
Nothing<bool>());
}
}
return Just(true);
}
namespace {
Maybe<bool> GenericTestIntegrityLevel(Handle<JSReceiver> receiver,
PropertyAttributes level) {
DCHECK(level == SEALED || level == FROZEN);
Maybe<bool> extensible = JSReceiver::IsExtensible(receiver);
MAYBE_RETURN(extensible, Nothing<bool>());
if (extensible.FromJust()) return Just(false);
Isolate* isolate = receiver->GetIsolate();
Handle<FixedArray> keys;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, keys, JSReceiver::OwnPropertyKeys(receiver), Nothing<bool>());
for (int i = 0; i < keys->length(); ++i) {
Handle<Object> key(keys->get(i), isolate);
PropertyDescriptor current_desc;
Maybe<bool> owned = JSReceiver::GetOwnPropertyDescriptor(
isolate, receiver, key, &current_desc);
MAYBE_RETURN(owned, Nothing<bool>());
if (owned.FromJust()) {
if (current_desc.configurable()) return Just(false);
if (level == FROZEN &&
PropertyDescriptor::IsDataDescriptor(&current_desc) &&
current_desc.writable()) {
return Just(false);
}
}
}
return Just(true);
}
} // namespace
Maybe<bool> JSReceiver::TestIntegrityLevel(Handle<JSReceiver> receiver,
IntegrityLevel level) {
if (!receiver->map().IsCustomElementsReceiverMap()) {
return JSObject::TestIntegrityLevel(Handle<JSObject>::cast(receiver),
level);
}
return GenericTestIntegrityLevel(receiver, level);
}
Maybe<bool> JSReceiver::PreventExtensions(Handle<JSReceiver> object,
ShouldThrow should_throw) {
if (object->IsJSProxy()) {
return JSProxy::PreventExtensions(Handle<JSProxy>::cast(object),
should_throw);
}
DCHECK(object->IsJSObject());
return JSObject::PreventExtensions(Handle<JSObject>::cast(object),
should_throw);
}
Maybe<bool> JSReceiver::IsExtensible(Handle<JSReceiver> object) {
if (object->IsJSProxy()) {
return JSProxy::IsExtensible(Handle<JSProxy>::cast(object));
}
return Just(JSObject::IsExtensible(Handle<JSObject>::cast(object)));
}
// static
MaybeHandle<Object> JSReceiver::ToPrimitive(Handle<JSReceiver> receiver,
ToPrimitiveHint hint) {
Isolate* const isolate = receiver->GetIsolate();
Handle<Object> exotic_to_prim;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, exotic_to_prim,
Object::GetMethod(receiver, isolate->factory()->to_primitive_symbol()),
Object);
if (!exotic_to_prim->IsUndefined(isolate)) {
Handle<Object> hint_string =
isolate->factory()->ToPrimitiveHintString(hint);
Handle<Object> result;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result,
Execution::Call(isolate, exotic_to_prim, receiver, 1, &hint_string),
Object);
if (result->IsPrimitive()) return result;
THROW_NEW_ERROR(isolate,
NewTypeError(MessageTemplate::kCannotConvertToPrimitive),
Object);
}
return OrdinaryToPrimitive(receiver, (hint == ToPrimitiveHint::kString)
? OrdinaryToPrimitiveHint::kString
: OrdinaryToPrimitiveHint::kNumber);
}
// static
MaybeHandle<Object> JSReceiver::OrdinaryToPrimitive(
Handle<JSReceiver> receiver, OrdinaryToPrimitiveHint hint) {
Isolate* const isolate = receiver->GetIsolate();
Handle<String> method_names[2];
switch (hint) {
case OrdinaryToPrimitiveHint::kNumber:
method_names[0] = isolate->factory()->valueOf_string();
method_names[1] = isolate->factory()->toString_string();
break;
case OrdinaryToPrimitiveHint::kString:
method_names[0] = isolate->factory()->toString_string();
method_names[1] = isolate->factory()->valueOf_string();
break;
}
for (Handle<String> name : method_names) {
Handle<Object> method;
ASSIGN_RETURN_ON_EXCEPTION(isolate, method,
JSReceiver::GetProperty(isolate, receiver, name),
Object);
if (method->IsCallable()) {
Handle<Object> result;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result,
Execution::Call(isolate, method, receiver, 0, nullptr), Object);
if (result->IsPrimitive()) return result;
}
}
THROW_NEW_ERROR(isolate,
NewTypeError(MessageTemplate::kCannotConvertToPrimitive),
Object);
}
V8_WARN_UNUSED_RESULT Maybe<bool> FastGetOwnValuesOrEntries(
Isolate* isolate, Handle<JSReceiver> receiver, bool get_entries,
Handle<FixedArray>* result) {
Handle<Map> map(JSReceiver::cast(*receiver).map(), isolate);
if (!map->IsJSObjectMap()) return Just(false);
if (!map->OnlyHasSimpleProperties()) return Just(false);
Handle<JSObject> object(JSObject::cast(*receiver), isolate);
Handle<DescriptorArray> descriptors(map->instance_descriptors(kRelaxedLoad),
isolate);
int number_of_own_descriptors = map->NumberOfOwnDescriptors();
size_t number_of_own_elements =
object->GetElementsAccessor()->GetCapacity(*object, object->elements());
if (number_of_own_elements >
static_cast<size_t>(FixedArray::kMaxLength - number_of_own_descriptors)) {
isolate->Throw(*isolate->factory()->NewRangeError(
MessageTemplate::kInvalidArrayLength));
return Nothing<bool>();
}
// The static cast is safe after the range check right above.
Handle<FixedArray> values_or_entries = isolate->factory()->NewFixedArray(
static_cast<int>(number_of_own_descriptors + number_of_own_elements));
int count = 0;
if (object->elements() != ReadOnlyRoots(isolate).empty_fixed_array()) {
MAYBE_RETURN(object->GetElementsAccessor()->CollectValuesOrEntries(
isolate, object, values_or_entries, get_entries, &count,
ENUMERABLE_STRINGS),
Nothing<bool>());
}
// We may have already lost stability, if CollectValuesOrEntries had
// side-effects.
bool stable = *map == object->map();
if (stable) {
descriptors.PatchValue(map->instance_descriptors(kRelaxedLoad));
}
for (InternalIndex index : InternalIndex::Range(number_of_own_descriptors)) {
HandleScope inner_scope(isolate);
Handle<Name> next_key(descriptors->GetKey(index), isolate);
if (!next_key->IsString()) continue;
Handle<Object> prop_value;
// Directly decode from the descriptor array if |from| did not change shape.
if (stable) {
DCHECK_EQ(object->map(), *map);
DCHECK_EQ(*descriptors, map->instance_descriptors(kRelaxedLoad));
PropertyDetails details = descriptors->GetDetails(index);
if (!details.IsEnumerable()) continue;
if (details.kind() == kData) {
if (details.location() == kDescriptor) {
prop_value = handle(descriptors->GetStrongValue(index), isolate);
} else {
Representation representation = details.representation();
FieldIndex field_index = FieldIndex::ForPropertyIndex(
*map, details.field_index(), representation);
prop_value =
JSObject::FastPropertyAt(object, representation, field_index);
}
} else {
LookupIterator it(isolate, object, next_key,
LookupIterator::OWN_SKIP_INTERCEPTOR);
DCHECK_EQ(LookupIterator::ACCESSOR, it.state());
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, prop_value, Object::GetProperty(&it), Nothing<bool>());
stable = object->map() == *map;
descriptors.PatchValue(map->instance_descriptors(kRelaxedLoad));
}
} else {
// If the map did change, do a slower lookup. We are still guaranteed that
// the object has a simple shape, and that the key is a name.
LookupIterator it(isolate, object, next_key,
LookupIterator::OWN_SKIP_INTERCEPTOR);
if (!it.IsFound()) continue;
DCHECK(it.state() == LookupIterator::DATA ||
it.state() == LookupIterator::ACCESSOR);
if (!it.IsEnumerable()) continue;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, prop_value, Object::GetProperty(&it), Nothing<bool>());
}
if (get_entries) {
prop_value = MakeEntryPair(isolate, next_key, prop_value);
}
values_or_entries->set(count, *prop_value);
count++;
}
DCHECK_LE(count, values_or_entries->length());
*result = FixedArray::ShrinkOrEmpty(isolate, values_or_entries, count);
return Just(true);
}
MaybeHandle<FixedArray> GetOwnValuesOrEntries(Isolate* isolate,
Handle<JSReceiver> object,
PropertyFilter filter,
bool try_fast_path,
bool get_entries) {
Handle<FixedArray> values_or_entries;
if (try_fast_path && filter == ENUMERABLE_STRINGS) {
Maybe<bool> fast_values_or_entries = FastGetOwnValuesOrEntries(
isolate, object, get_entries, &values_or_entries);
if (fast_values_or_entries.IsNothing()) return MaybeHandle<FixedArray>();
if (fast_values_or_entries.FromJust()) return values_or_entries;
}
PropertyFilter key_filter =
static_cast<PropertyFilter>(filter & ~ONLY_ENUMERABLE);
Handle<FixedArray> keys;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, keys,
KeyAccumulator::GetKeys(object, KeyCollectionMode::kOwnOnly, key_filter,
GetKeysConversion::kConvertToString),
MaybeHandle<FixedArray>());
values_or_entries = isolate->factory()->NewFixedArray(keys->length());
int length = 0;
for (int i = 0; i < keys->length(); ++i) {
Handle<Name> key =
Handle<Name>::cast(handle(keys->get(isolate, i), isolate));
if (filter & ONLY_ENUMERABLE) {
PropertyDescriptor descriptor;
Maybe<bool> did_get_descriptor = JSReceiver::GetOwnPropertyDescriptor(
isolate, object, key, &descriptor);
MAYBE_RETURN(did_get_descriptor, MaybeHandle<FixedArray>());
if (!did_get_descriptor.FromJust() || !descriptor.enumerable()) continue;
}
Handle<Object> value;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, value, Object::GetPropertyOrElement(isolate, object, key),
MaybeHandle<FixedArray>());
if (get_entries) {
Handle<FixedArray> entry_storage =
isolate->factory()->NewUninitializedFixedArray(2);
entry_storage->set(0, *key);
entry_storage->set(1, *value);
value = isolate->factory()->NewJSArrayWithElements(entry_storage,
PACKED_ELEMENTS, 2);
}
values_or_entries->set(length, *value);
length++;
}
DCHECK_LE(length, values_or_entries->length());
return FixedArray::ShrinkOrEmpty(isolate, values_or_entries, length);
}
MaybeHandle<FixedArray> JSReceiver::GetOwnValues(Handle<JSReceiver> object,
PropertyFilter filter,
bool try_fast_path) {
return GetOwnValuesOrEntries(object->GetIsolate(), object, filter,
try_fast_path, false);
}
MaybeHandle<FixedArray> JSReceiver::GetOwnEntries(Handle<JSReceiver> object,
PropertyFilter filter,
bool try_fast_path) {
return GetOwnValuesOrEntries(object->GetIsolate(), object, filter,
try_fast_path, true);
}
Maybe<bool> JSReceiver::SetPrototype(Handle<JSReceiver> object,
Handle<Object> value, bool from_javascript,
ShouldThrow should_throw) {
if (object->IsJSProxy()) {
return JSProxy::SetPrototype(Handle<JSProxy>::cast(object), value,
from_javascript, should_throw);
}
return JSObject::SetPrototype(Handle<JSObject>::cast(object), value,
from_javascript, should_throw);
}
bool JSReceiver::HasProxyInPrototype(Isolate* isolate) {
for (PrototypeIterator iter(isolate, *this, kStartAtReceiver,
PrototypeIterator::END_AT_NULL);
!iter.IsAtEnd(); iter.AdvanceIgnoringProxies()) {
if (iter.GetCurrent().IsJSProxy()) return true;
}
return false;
}
bool JSReceiver::IsCodeLike(Isolate* isolate) const {
DisallowGarbageCollection no_gc;
Object maybe_constructor = map().GetConstructor();
if (!maybe_constructor.IsJSFunction()) return false;
if (!JSFunction::cast(maybe_constructor).shared().IsApiFunction()) {
return false;
}
Object instance_template = JSFunction::cast(maybe_constructor)
.shared()
.get_api_func_data()
.GetInstanceTemplate();
if (instance_template.IsUndefined(isolate)) return false;
return ObjectTemplateInfo::cast(instance_template).code_like();
}
// static
MaybeHandle<JSObject> JSObject::New(Handle<JSFunction> constructor,
Handle<JSReceiver> new_target,
Handle<AllocationSite> site) {
// If called through new, new.target can be:
// - a subclass of constructor,
// - a proxy wrapper around constructor, or
// - the constructor itself.
// If called through Reflect.construct, it's guaranteed to be a constructor.
Isolate* const isolate = constructor->GetIsolate();
DCHECK(constructor->IsConstructor());
DCHECK(new_target->IsConstructor());
DCHECK(!constructor->has_initial_map() ||
constructor->initial_map().instance_type() != JS_FUNCTION_TYPE);
Handle<Map> initial_map;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, initial_map,
JSFunction::GetDerivedMap(isolate, constructor, new_target), JSObject);
int initial_capacity = V8_DICT_MODE_PROTOTYPES_BOOL
? OrderedNameDictionary::kInitialCapacity
: NameDictionary::kInitialCapacity;
Handle<JSObject> result = isolate->factory()->NewFastOrSlowJSObjectFromMap(
initial_map, initial_capacity, AllocationType::kYoung, site);
isolate->counters()->constructed_objects()->Increment();
isolate->counters()->constructed_objects_runtime()->Increment();
return result;
}
// 9.1.12 ObjectCreate ( proto [ , internalSlotsList ] )
// Notice: This is NOT 19.1.2.2 Object.create ( O, Properties )
MaybeHandle<JSObject> JSObject::ObjectCreate(Isolate* isolate,
Handle<Object> prototype) {
// Generate the map with the specified {prototype} based on the Object
// function's initial map from the current native context.
// TODO(bmeurer): Use a dedicated cache for Object.create; think about
// slack tracking for Object.create.
Handle<Map> map =
Map::GetObjectCreateMap(isolate, Handle<HeapObject>::cast(prototype));
// Actually allocate the object.
return isolate->factory()->NewFastOrSlowJSObjectFromMap(map);
}
void JSObject::EnsureWritableFastElements(Handle<JSObject> object) {
DCHECK(object->HasSmiOrObjectElements() ||
object->HasFastStringWrapperElements() ||
object->HasAnyNonextensibleElements());
FixedArray raw_elems = FixedArray::cast(object->elements());
Isolate* isolate = object->GetIsolate();
if (raw_elems.map() != ReadOnlyRoots(isolate).fixed_cow_array_map()) return;
Handle<FixedArray> elems(raw_elems, isolate);
Handle<FixedArray> writable_elems = isolate->factory()->CopyFixedArrayWithMap(
elems, isolate->factory()->fixed_array_map());
object->set_elements(*writable_elems);
isolate->counters()->cow_arrays_converted()->Increment();
}
int JSObject::GetHeaderSize(InstanceType type,
bool function_has_prototype_slot) {
switch (type) {
case JS_OBJECT_TYPE:
case JS_API_OBJECT_TYPE:
case JS_SPECIAL_API_OBJECT_TYPE:
return JSObject::kHeaderSize;
case JS_GENERATOR_OBJECT_TYPE:
return JSGeneratorObject::kHeaderSize;
case JS_ASYNC_FUNCTION_OBJECT_TYPE:
return JSAsyncFunctionObject::kHeaderSize;
case JS_ASYNC_GENERATOR_OBJECT_TYPE:
return JSAsyncGeneratorObject::kHeaderSize;
case JS_ASYNC_FROM_SYNC_ITERATOR_TYPE:
return JSAsyncFromSyncIterator::kHeaderSize;
case JS_GLOBAL_PROXY_TYPE:
return JSGlobalProxy::kHeaderSize;
case JS_GLOBAL_OBJECT_TYPE:
return JSGlobalObject::kHeaderSize;
case JS_BOUND_FUNCTION_TYPE:
return JSBoundFunction::kHeaderSize;
case JS_FUNCTION_TYPE:
return JSFunction::GetHeaderSize(function_has_prototype_slot);
case JS_PRIMITIVE_WRAPPER_TYPE:
return JSPrimitiveWrapper::kHeaderSize;
case JS_DATE_TYPE:
return JSDate::kHeaderSize;
case JS_ARRAY_TYPE:
return JSArray::kHeaderSize;
case JS_ARRAY_BUFFER_TYPE:
return JSArrayBuffer::kHeaderSize;
case JS_ARRAY_ITERATOR_TYPE:
return JSArrayIterator::kHeaderSize;
case JS_TYPED_ARRAY_TYPE:
return JSTypedArray::kHeaderSize;
case JS_DATA_VIEW_TYPE:
return JSDataView::kHeaderSize;
case JS_SET_TYPE:
return JSSet::kHeaderSize;
case JS_MAP_TYPE:
return JSMap::kHeaderSize;
case JS_SET_KEY_VALUE_ITERATOR_TYPE:
case JS_SET_VALUE_ITERATOR_TYPE:
return JSSetIterator::kHeaderSize;
case JS_MAP_KEY_ITERATOR_TYPE:
case JS_MAP_KEY_VALUE_ITERATOR_TYPE:
case JS_MAP_VALUE_ITERATOR_TYPE:
return JSMapIterator::kHeaderSize;
case JS_WEAK_REF_TYPE:
return JSWeakRef::kHeaderSize;
case JS_FINALIZATION_REGISTRY_TYPE:
return JSFinalizationRegistry::kHeaderSize;
case JS_WEAK_MAP_TYPE:
return JSWeakMap::kHeaderSize;
case JS_WEAK_SET_TYPE:
return JSWeakSet::kHeaderSize;
case JS_PROMISE_TYPE:
return JSPromise::kHeaderSize;
case JS_REG_EXP_TYPE:
return JSRegExp::kHeaderSize;
case JS_REG_EXP_STRING_ITERATOR_TYPE:
return JSRegExpStringIterator::kHeaderSize;
case JS_CONTEXT_EXTENSION_OBJECT_TYPE:
return JSObject::kHeaderSize;
case JS_MESSAGE_OBJECT_TYPE:
return JSMessageObject::kHeaderSize;
case JS_ARGUMENTS_OBJECT_TYPE:
return JSObject::kHeaderSize;
case JS_ERROR_TYPE:
return JSObject::kHeaderSize;
case JS_STRING_ITERATOR_TYPE:
return JSStringIterator::kHeaderSize;
case JS_MODULE_NAMESPACE_TYPE:
return JSModuleNamespace::kHeaderSize;
#ifdef V8_INTL_SUPPORT
case JS_V8_BREAK_ITERATOR_TYPE:
return JSV8BreakIterator::kHeaderSize;
case JS_COLLATOR_TYPE:
return JSCollator::kHeaderSize;
case JS_DATE_TIME_FORMAT_TYPE:
return JSDateTimeFormat::kHeaderSize;
case JS_DISPLAY_NAMES_TYPE:
return JSDisplayNames::kHeaderSize;
case JS_LIST_FORMAT_TYPE:
return JSListFormat::kHeaderSize;
case JS_LOCALE_TYPE:
return JSLocale::kHeaderSize;
case JS_NUMBER_FORMAT_TYPE:
return JSNumberFormat::kHeaderSize;
case JS_PLURAL_RULES_TYPE:
return JSPluralRules::kHeaderSize;
case JS_RELATIVE_TIME_FORMAT_TYPE:
return JSRelativeTimeFormat::kHeaderSize;
case JS_SEGMENT_ITERATOR_TYPE:
return JSSegmentIterator::kHeaderSize;
case JS_SEGMENTER_TYPE:
return JSSegmenter::kHeaderSize;
case JS_SEGMENTS_TYPE:
return JSSegments::kHeaderSize;
#endif // V8_INTL_SUPPORT
case WASM_GLOBAL_OBJECT_TYPE:
return WasmGlobalObject::kHeaderSize;
case WASM_INSTANCE_OBJECT_TYPE:
return WasmInstanceObject::kHeaderSize;
case WASM_MEMORY_OBJECT_TYPE:
return WasmMemoryObject::kHeaderSize;
case WASM_MODULE_OBJECT_TYPE:
return WasmModuleObject::kHeaderSize;
case WASM_TABLE_OBJECT_TYPE:
return WasmTableObject::kHeaderSize;
case WASM_EXCEPTION_OBJECT_TYPE:
return WasmExceptionObject::kHeaderSize;
default:
UNREACHABLE();
}
}
// static
bool JSObject::AllCanRead(LookupIterator* it) {
// Skip current iteration, it's in state ACCESS_CHECK or INTERCEPTOR, both of
// which have already been checked.
DCHECK(it->state() == LookupIterator::ACCESS_CHECK ||
it->state() == LookupIterator::INTERCEPTOR);
for (it->Next(); it->IsFound(); it->Next()) {
if (it->state() == LookupIterator::ACCESSOR) {
auto accessors = it->GetAccessors();
if (accessors->IsAccessorInfo()) {
if (AccessorInfo::cast(*accessors).all_can_read()) return true;
}
} else if (it->state() == LookupIterator::INTERCEPTOR) {
if (it->GetInterceptor()->all_can_read()) return true;
} else if (it->state() == LookupIterator::JSPROXY) {
// Stop lookupiterating. And no, AllCanNotRead.
return false;
}
}
return false;
}
MaybeHandle<Object> JSObject::GetPropertyWithFailedAccessCheck(
LookupIterator* it) {
Isolate* isolate = it->isolate();
Handle<JSObject> checked = it->GetHolder<JSObject>();
Handle<InterceptorInfo> interceptor =
it->GetInterceptorForFailedAccessCheck();
if (interceptor.is_null()) {
while (AllCanRead(it)) {
if (it->state() == LookupIterator::ACCESSOR) {
return Object::GetPropertyWithAccessor(it);
}
DCHECK_EQ(LookupIterator::INTERCEPTOR, it->state());
bool done;
Handle<Object> result;
ASSIGN_RETURN_ON_EXCEPTION(isolate, result,
GetPropertyWithInterceptor(it, &done), Object);
if (done) return result;
}
} else {
Handle<Object> result;
bool done;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result,
GetPropertyWithInterceptorInternal(it, interceptor, &done), Object);
if (done) return result;
}
// Cross-Origin [[Get]] of Well-Known Symbols does not throw, and returns
// undefined.
Handle<Name> name = it->GetName();
if (name->IsSymbol() && Symbol::cast(*name).is_well_known_symbol()) {
return it->factory()->undefined_value();
}
isolate->ReportFailedAccessCheck(checked);
RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION(isolate, Object);
return it->factory()->undefined_value();
}
Maybe<PropertyAttributes> JSObject::GetPropertyAttributesWithFailedAccessCheck(
LookupIterator* it) {
Isolate* isolate = it->isolate();
Handle<JSObject> checked = it->GetHolder<JSObject>();
Handle<InterceptorInfo> interceptor =
it->GetInterceptorForFailedAccessCheck();
if (interceptor.is_null()) {
while (AllCanRead(it)) {
if (it->state() == LookupIterator::ACCESSOR) {
return Just(it->property_attributes());
}
DCHECK_EQ(LookupIterator::INTERCEPTOR, it->state());
auto result = GetPropertyAttributesWithInterceptor(it);
if (isolate->has_scheduled_exception()) break;
if (result.IsJust() && result.FromJust() != ABSENT) return result;
}
} else {
Maybe<PropertyAttributes> result =
GetPropertyAttributesWithInterceptorInternal(it, interceptor);
if (isolate->has_pending_exception()) return Nothing<PropertyAttributes>();
if (result.FromMaybe(ABSENT) != ABSENT) return result;
}
isolate->ReportFailedAccessCheck(checked);
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, Nothing<PropertyAttributes>());
return Just(ABSENT);
}
// static
bool JSObject::AllCanWrite(LookupIterator* it) {
for (; it->IsFound() && it->state() != LookupIterator::JSPROXY; it->Next()) {
if (it->state() == LookupIterator::ACCESSOR) {
Handle<Object> accessors = it->GetAccessors();
if (accessors->IsAccessorInfo()) {
if (AccessorInfo::cast(*accessors).all_can_write()) return true;
}
}
}
return false;
}
Maybe<bool> JSObject::SetPropertyWithFailedAccessCheck(
LookupIterator* it, Handle<Object> value, Maybe<ShouldThrow> should_throw) {
Isolate* isolate = it->isolate();
Handle<JSObject> checked = it->GetHolder<JSObject>();
Handle<InterceptorInfo> interceptor =
it->GetInterceptorForFailedAccessCheck();
if (interceptor.is_null()) {
if (AllCanWrite(it)) {
return Object::SetPropertyWithAccessor(it, value, should_throw);
}
} else {
Maybe<bool> result = SetPropertyWithInterceptorInternal(
it, interceptor, should_throw, value);
if (isolate->has_pending_exception()) return Nothing<bool>();
if (result.IsJust()) return result;
}
isolate->ReportFailedAccessCheck(checked);
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, Nothing<bool>());
return Just(true);
}
void JSObject::SetNormalizedProperty(Handle<JSObject> object, Handle<Name> name,
Handle<Object> value,
PropertyDetails details) {
DCHECK(!object->HasFastProperties());
DCHECK(name->IsUniqueName());
Isolate* isolate = object->GetIsolate();
uint32_t hash = name->Hash();
if (object->IsJSGlobalObject()) {
Handle<JSGlobalObject> global_obj = Handle<JSGlobalObject>::cast(object);
Handle<GlobalDictionary> dictionary(global_obj->global_dictionary(),
isolate);
ReadOnlyRoots roots(isolate);
InternalIndex entry = dictionary->FindEntry(isolate, roots, name, hash);
if (entry.is_not_found()) {
DCHECK_IMPLIES(global_obj->map().is_prototype_map(),
Map::IsPrototypeChainInvalidated(global_obj->map()));
auto cell = isolate->factory()->NewPropertyCell(name);
cell->set_value(*value);
auto cell_type = value->IsUndefined(roots) ? PropertyCellType::kUndefined
: PropertyCellType::kConstant;
details = details.set_cell_type(cell_type);
value = cell;
dictionary =
GlobalDictionary::Add(isolate, dictionary, name, value, details);
global_obj->set_global_dictionary(*dictionary);
} else {
Handle<PropertyCell> cell = PropertyCell::PrepareForValue(
isolate, dictionary, entry, value, details);
cell->set_value(*value);
}
} else {
if (V8_DICT_MODE_PROTOTYPES_BOOL) {
Handle<OrderedNameDictionary> dictionary(
object->property_dictionary_ordered(), isolate);
InternalIndex entry = dictionary->FindEntry(isolate, *name);
if (entry.is_not_found()) {
DCHECK_IMPLIES(object->map().is_prototype_map(),
Map::IsPrototypeChainInvalidated(object->map()));
dictionary = OrderedNameDictionary::Add(isolate, dictionary, name,
value, details)
.ToHandleChecked();
object->SetProperties(*dictionary);
} else {
dictionary->SetEntry(entry, *name, *value, details);
}
} else {
Handle<NameDictionary> dictionary(object->property_dictionary(), isolate);
InternalIndex entry = dictionary->FindEntry(isolate, name);
if (entry.is_not_found()) {
DCHECK_IMPLIES(object->map().is_prototype_map(),
Map::IsPrototypeChainInvalidated(object->map()));
dictionary =
NameDictionary::Add(isolate, dictionary, name, value, details);
object->SetProperties(*dictionary);
} else {
PropertyDetails original_details = dictionary->DetailsAt(entry);
int enumeration_index = original_details.dictionary_index();
DCHECK_GT(enumeration_index, 0);
details = details.set_index(enumeration_index);
dictionary->SetEntry(entry, *name, *value, details);
}
}
}
}
void JSObject::JSObjectShortPrint(StringStream* accumulator) {
switch (map().instance_type()) {
case JS_ARRAY_TYPE: {
double length = JSArray::cast(*this).length().IsUndefined()
? 0
: JSArray::cast(*this).length().Number();
accumulator->Add("<JSArray[%u]>", static_cast<uint32_t>(length));
break;
}
case JS_BOUND_FUNCTION_TYPE: {
JSBoundFunction bound_function = JSBoundFunction::cast(*this);
accumulator->Add("<JSBoundFunction");
accumulator->Add(" (BoundTargetFunction %p)>",
reinterpret_cast<void*>(
bound_function.bound_target_function().ptr()));
break;
}
case JS_WEAK_MAP_TYPE: {
accumulator->Add("<JSWeakMap>");
break;
}
case JS_WEAK_SET_TYPE: {
accumulator->Add("<JSWeakSet>");
break;
}
case JS_REG_EXP_TYPE: {
accumulator->Add("<JSRegExp");
JSRegExp regexp = JSRegExp::cast(*this);
if (regexp.source().IsString()) {
accumulator->Add(" ");
String::cast(regexp.source()).StringShortPrint(accumulator);
}
accumulator->Add(">");
break;
}
case JS_FUNCTION_TYPE: {
JSFunction function = JSFunction::cast(*this);
Object fun_name = function.shared().DebugName();
bool printed = false;
if (fun_name.IsString()) {
String str = String::cast(fun_name);
if (str.length() > 0) {
accumulator->Add("<JSFunction ");
accumulator->Put(str);
printed = true;
}
}
if (!printed) {
accumulator->Add("<JSFunction");
}
if (FLAG_trace_file_names) {
Object source_name = Script::cast(function.shared().script()).name();
if (source_name.IsString()) {
String str = String::cast(source_name);
if (str.length() > 0) {
accumulator->Add(" <");
accumulator->Put(str);
accumulator->Add(">");
}
}
}
accumulator->Add(" (sfi = %p)",
reinterpret_cast<void*>(function.shared().ptr()));
accumulator->Put('>');
break;
}
case JS_GENERATOR_OBJECT_TYPE: {
accumulator->Add("<JSGenerator>");
break;
}
case JS_ASYNC_FUNCTION_OBJECT_TYPE: {
accumulator->Add("<JSAsyncFunctionObject>");
break;
}
case JS_ASYNC_GENERATOR_OBJECT_TYPE: {
accumulator->Add("<JS AsyncGenerator>");
break;
}
// All other JSObjects are rather similar to each other (JSObject,
// JSGlobalProxy, JSGlobalObject, JSUndetectable, JSPrimitiveWrapper).
default: {
Map map_of_this = map();
Heap* heap = GetHeap();
Object constructor = map_of_this.GetConstructor();
bool printed = false;
if (constructor.IsHeapObject() &&
!heap->Contains(HeapObject::cast(constructor))) {
accumulator->Add("!!!INVALID CONSTRUCTOR!!!");
} else {
bool global_object = IsJSGlobalProxy();
if (constructor.IsJSFunction()) {
if (!heap->Contains(JSFunction::cast(constructor).shared())) {
accumulator->Add("!!!INVALID SHARED ON CONSTRUCTOR!!!");
} else {
String constructor_name =
JSFunction::cast(constructor).shared().Name();
if (constructor_name.length() > 0) {
accumulator->Add(global_object ? "<GlobalObject " : "<");
accumulator->Put(constructor_name);
accumulator->Add(" %smap = %p",
map_of_this.is_deprecated() ? "deprecated-" : "",
map_of_this);
printed = true;
}
}
} else if (constructor.IsFunctionTemplateInfo()) {
accumulator->Add(global_object ? "<RemoteObject>" : "<RemoteObject>");
printed = true;
}
if (!printed) {
accumulator->Add("<JS%sObject", global_object ? "Global " : "");
}
}
if (IsJSPrimitiveWrapper()) {
accumulator->Add(" value = ");
JSPrimitiveWrapper::cast(*this).value().ShortPrint(accumulator);
}
accumulator->Put('>');
break;
}
}
}
void JSObject::PrintElementsTransition(FILE* file, Handle<JSObject> object,
ElementsKind from_kind,
Handle<FixedArrayBase> from_elements,
ElementsKind to_kind,
Handle<FixedArrayBase> to_elements) {
if (from_kind != to_kind) {
OFStream os(file);
os << "elements transition [" << ElementsKindToString(from_kind) << " -> "
<< ElementsKindToString(to_kind) << "] in ";
JavaScriptFrame::PrintTop(object->GetIsolate(), file, false, true);
PrintF(file, " for ");
object->ShortPrint(file);
PrintF(file, " from ");
from_elements->ShortPrint(file);
PrintF(file, " to ");
to_elements->ShortPrint(file);
PrintF(file, "\n");
}
}
void JSObject::PrintInstanceMigration(FILE* file, Map original_map,
Map new_map) {
if (new_map.is_dictionary_map()) {
PrintF(file, "[migrating to slow]\n");
return;
}
PrintF(file, "[migrating]");
DescriptorArray o = original_map.instance_descriptors(kRelaxedLoad);
DescriptorArray n = new_map.instance_descriptors(kRelaxedLoad);
for (InternalIndex i : original_map.IterateOwnDescriptors()) {
Representation o_r = o.GetDetails(i).representation();
Representation n_r = n.GetDetails(i).representation();
if (!o_r.Equals(n_r)) {
String::cast(o.GetKey(i)).PrintOn(file);
PrintF(file, ":%s->%s ", o_r.Mnemonic(), n_r.Mnemonic());
} else if (o.GetDetails(i).location() == kDescriptor &&
n.GetDetails(i).location() == kField) {
Name name = o.GetKey(i);
if (name.IsString()) {
String::cast(name).PrintOn(file);
} else {
PrintF(file, "{symbol %p}", reinterpret_cast<void*>(name.ptr()));
}
PrintF(file, " ");
}
}
if (original_map.elements_kind() != new_map.elements_kind()) {
PrintF(file, "elements_kind[%i->%i]", original_map.elements_kind(),
new_map.elements_kind());
}
PrintF(file, "\n");
}
bool JSObject::IsUnmodifiedApiObject(FullObjectSlot o) {
Object object = *o;
if (object.IsSmi()) return false;
HeapObject heap_object = HeapObject::cast(object);
if (!object.IsJSObject()) return false;
JSObject js_object = JSObject::cast(object);
if (!js_object.IsDroppableApiWrapper()) return false;
Object maybe_constructor = js_object.map().GetConstructor();
if (!maybe_constructor.IsJSFunction()) return false;
JSFunction constructor = JSFunction::cast(maybe_constructor);
if (js_object.elements().length() != 0) return false;
// Check that the object is not a key in a WeakMap (over-approximation).
if (!js_object.GetIdentityHash().IsUndefined()) return false;
return constructor.initial_map() == heap_object.map();
}
// static
void JSObject::UpdatePrototypeUserRegistration(Handle<Map> old_map,
Handle<Map> new_map,
Isolate* isolate) {
DCHECK(old_map->is_prototype_map());
DCHECK(new_map->is_prototype_map());
bool was_registered = JSObject::UnregisterPrototypeUser(old_map, isolate);
new_map->set_prototype_info(old_map->prototype_info());
old_map->set_prototype_info(Smi::zero());
if (FLAG_trace_prototype_users) {
PrintF("Moving prototype_info %p from map %p to map %p.\n",
reinterpret_cast<void*>(new_map->prototype_info().ptr()),
reinterpret_cast<void*>(old_map->ptr()),
reinterpret_cast<void*>(new_map->ptr()));
}
if (was_registered) {
if (new_map->prototype_info().IsPrototypeInfo()) {
// The new map isn't registered with its prototype yet; reflect this fact
// in the PrototypeInfo it just inherited from the old map.
PrototypeInfo::cast(new_map->prototype_info())
.set_registry_slot(PrototypeInfo::UNREGISTERED);
}
JSObject::LazyRegisterPrototypeUser(new_map, isolate);
}
}
// static
void JSObject::NotifyMapChange(Handle<Map> old_map, Handle<Map> new_map,
Isolate* isolate) {
if (!old_map->is_prototype_map()) return;
InvalidatePrototypeChains(*old_map);
// If the map was registered with its prototype before, ensure that it
// registers with its new prototype now. This preserves the invariant that
// when a map on a prototype chain is registered with its prototype, then
// all prototypes further up the chain are also registered with their
// respective prototypes.
UpdatePrototypeUserRegistration(old_map, new_map, isolate);
}
namespace {
// To migrate a fast instance to a fast map:
// - First check whether the instance needs to be rewritten. If not, simply
// change the map.
// - Otherwise, allocate a fixed array large enough to hold all fields, in
// addition to unused space.
// - Copy all existing properties in, in the following order: backing store
// properties, unused fields, inobject properties.
// - If all allocation succeeded, commit the state atomically:
// * Copy inobject properties from the backing store back into the object.
// * Trim the difference in instance size of the object. This also cleanly
// frees inobject properties that moved to the backing store.
// * If there are properties left in the backing store, trim of the space used
// to temporarily store the inobject properties.
// * If there are properties left in the backing store, install the backing
// store.
void MigrateFastToFast(Isolate* isolate, Handle<JSObject> object,
Handle<Map> new_map) {
Handle<Map> old_map(object->map(), isolate);
// In case of a regular transition.
if (new_map->GetBackPointer(isolate) == *old_map) {
// If the map does not add named properties, simply set the map.
if (old_map->NumberOfOwnDescriptors() ==
new_map->NumberOfOwnDescriptors()) {
object->synchronized_set_map(*new_map);
return;
}
// If the map adds a new kDescriptor property, simply set the map.
PropertyDetails details = new_map->GetLastDescriptorDetails(isolate);
if (details.location() == kDescriptor) {
object->synchronized_set_map(*new_map);
return;
}
// Check if we still have space in the {object}, in which case we
// can also simply set the map (modulo a special case for mutable
// double boxes).
FieldIndex index =
FieldIndex::ForDescriptor(isolate, *new_map, new_map->LastAdded());
if (index.is_inobject() || index.outobject_array_index() <
object->property_array(isolate).length()) {
// We still need to allocate HeapNumbers for double fields
// if either double field unboxing is disabled or the double field
// is in the PropertyArray backing store (where we don't support
// double field unboxing).
if (index.is_double() && !new_map->IsUnboxedDoubleField(isolate, index)) {
auto value = isolate->factory()->NewHeapNumberWithHoleNaN();
object->RawFastPropertyAtPut(index, *value);
}
object->synchronized_set_map(*new_map);
return;
}
// This migration is a transition from a map that has run out of property
// space. Extend the backing store.
int grow_by = new_map->UnusedPropertyFields() + 1;
Handle<PropertyArray> old_storage(object->property_array(isolate), isolate);
Handle<PropertyArray> new_storage =
isolate->factory()->CopyPropertyArrayAndGrow(old_storage, grow_by);
// Properly initialize newly added property.
Handle<Object> value;
if (details.representation().IsDouble()) {
value = isolate->factory()->NewHeapNumberWithHoleNaN();
} else {
value = isolate->factory()->uninitialized_value();
}
DCHECK_EQ(kField, details.location());
DCHECK_EQ(kData, details.kind());
DCHECK(!index.is_inobject()); // Must be a backing store index.
new_storage->set(index.outobject_array_index(), *value);
// From here on we cannot fail and we shouldn't GC anymore.
DisallowHeapAllocation no_allocation;
// Set the new property value and do the map transition.
object->SetProperties(*new_storage);
object->synchronized_set_map(*new_map);
return;
}
int old_number_of_fields;
int number_of_fields = new_map->NumberOfFields();
int inobject = new_map->GetInObjectProperties();
int unused = new_map->UnusedPropertyFields();
// Nothing to do if no functions were converted to fields and no smis were
// converted to doubles.
if (!old_map->InstancesNeedRewriting(*new_map, number_of_fields, inobject,
unused, &old_number_of_fields)) {
object->synchronized_set_map(*new_map);
return;
}
int total_size = number_of_fields + unused;
int external = total_size - inobject;
Handle<PropertyArray> array = isolate->factory()->NewPropertyArray(external);
// We use this array to temporarily store the inobject properties.
Handle<FixedArray> inobject_props =
isolate->factory()->NewFixedArray(inobject);
Handle<DescriptorArray> old_descriptors(
old_map->instance_descriptors(isolate, kRelaxedLoad), isolate);
Handle<DescriptorArray> new_descriptors(
new_map->instance_descriptors(isolate, kRelaxedLoad), isolate);
int old_nof = old_map->NumberOfOwnDescriptors();
int new_nof = new_map->NumberOfOwnDescriptors();
// This method only supports generalizing instances to at least the same
// number of properties.
DCHECK(old_nof <= new_nof);
for (InternalIndex i : InternalIndex::Range(old_nof)) {
PropertyDetails details = new_descriptors->GetDetails(i);
if (details.location() != kField) continue;
DCHECK_EQ(kData, details.kind());
PropertyDetails old_details = old_descriptors->GetDetails(i);
Representation old_representation = old_details.representation();
Representation representation = details.representation();
Handle<Object> value;
if (old_details.location() == kDescriptor) {
if (old_details.kind() == kAccessor) {
// In case of kAccessor -> kData property reconfiguration, the property
// must already be prepared for data of certain type.
DCHECK(!details.representation().IsNone());
if (details.representation().IsDouble()) {
value = isolate->factory()->NewHeapNumberWithHoleNaN();
} else {
value = isolate->factory()->uninitialized_value();
}
} else {
DCHECK_EQ(kData, old_details.kind());
value = handle(old_descriptors->GetStrongValue(isolate, i), isolate);
DCHECK(!old_representation.IsDouble() && !representation.IsDouble());
}
} else {
DCHECK_EQ(kField, old_details.location());
FieldIndex index = FieldIndex::ForDescriptor(isolate, *old_map, i);
if (object->IsUnboxedDoubleField(isolate, index)) {
uint64_t old_bits = object->RawFastDoublePropertyAsBitsAt(index);
value = isolate->factory()->NewHeapNumberFromBits(old_bits);
} else {
value = handle(object->RawFastPropertyAt(isolate, index), isolate);
if (!old_representation.IsDouble() && representation.IsDouble()) {
DCHECK_IMPLIES(old_representation.IsNone(),
value->IsUninitialized(isolate));
value = Object::NewStorageFor(isolate, value, representation);
} else if (old_representation.IsDouble() &&
!representation.IsDouble()) {
value = Object::WrapForRead(isolate, value, old_representation);
}
}
}
DCHECK(!(representation.IsDouble() && value->IsSmi()));
int target_index = new_descriptors->GetFieldIndex(i);
if (target_index < inobject) {
inobject_props->set(target_index, *value);
} else {
array->set(target_index - inobject, *value);
}
}
for (InternalIndex i : InternalIndex::Range(old_nof, new_nof)) {
PropertyDetails details = new_descriptors->GetDetails(i);
if (details.location() != kField) continue;
DCHECK_EQ(kData, details.kind());
Handle<Object> value;
if (details.representation().IsDouble()) {
value = isolate->factory()->NewHeapNumberWithHoleNaN();
} else {
value = isolate->factory()->uninitialized_value();
}
int target_index = new_descriptors->GetFieldIndex(i);
if (target_index < inobject) {
inobject_props->set(target_index, *value);
} else {
array->set(target_index - inobject, *value);
}
}
// From here on we cannot fail and we shouldn't GC anymore.
DisallowHeapAllocation no_allocation;
Heap* heap = isolate->heap();
// Invalidate slots manually later in case of tagged to untagged translation.
// In all other cases the recorded slot remains dereferenceable.
heap->NotifyObjectLayoutChange(*object, no_allocation,
InvalidateRecordedSlots::kNo);
// Copy (real) inobject properties. If necessary, stop at number_of_fields to
// avoid overwriting |one_pointer_filler_map|.
int limit = std::min(inobject, number_of_fields);
for (int i = 0; i < limit; i++) {
FieldIndex index = FieldIndex::ForPropertyIndex(*new_map, i);
Object value = inobject_props->get(isolate, i);
// Can't use JSObject::FastPropertyAtPut() because proper map was not set
// yet.
if (new_map->IsUnboxedDoubleField(isolate, index)) {
DCHECK(value.IsHeapNumber(isolate));
// Ensure that all bits of the double value are preserved.
object->RawFastDoublePropertyAsBitsAtPut(
index, HeapNumber::cast(value).value_as_bits());
if (i < old_number_of_fields && !old_map->IsUnboxedDoubleField(index)) {
// Transition from tagged to untagged slot.
MemoryChunk* chunk = MemoryChunk::FromHeapObject(*object);
chunk->InvalidateRecordedSlots(*object);
} else {
#ifdef DEBUG
heap->VerifyClearedSlot(*object, object->RawField(index.offset()));
#endif
}
} else {
object->RawFastPropertyAtPut(index, value);
}
}
object->SetProperties(*array);
// Create filler object past the new instance size.
int old_instance_size = old_map->instance_size();
int new_instance_size = new_map->instance_size();
int instance_size_delta = old_instance_size - new_instance_size;
DCHECK_GE(instance_size_delta, 0);
if (instance_size_delta > 0) {
Address address = object->address();
heap->CreateFillerObjectAt(address + new_instance_size, instance_size_delta,
ClearRecordedSlots::kYes);
}
// We are storing the new map using release store after creating a filler for
// the left-over space to avoid races with the sweeper thread.
object->synchronized_set_map(*new_map);
}
void MigrateFastToSlow(Isolate* isolate, Handle<JSObject> object,
Handle<Map> new_map,
int expected_additional_properties) {
// The global object is always normalized.
DCHECK(!object->IsJSGlobalObject(isolate));
// JSGlobalProxy must never be normalized
DCHECK(!object->IsJSGlobalProxy(isolate));
DCHECK_IMPLIES(new_map->is_prototype_map(),
Map::IsPrototypeChainInvalidated(*new_map));
HandleScope scope(isolate);
Handle<Map> map(object->map(isolate), isolate);
// Allocate new content.
int real_size = map->NumberOfOwnDescriptors();
int property_count = real_size;
if (expected_additional_properties > 0) {
property_count += expected_additional_properties;
} else {
// Make space for two more properties.
int initial_capacity = V8_DICT_MODE_PROTOTYPES_BOOL
? OrderedNameDictionary::kInitialCapacity
: NameDictionary::kInitialCapacity;
property_count += initial_capacity;
}
Handle<NameDictionary> dictionary;
Handle<OrderedNameDictionary> ord_dictionary;
if (V8_DICT_MODE_PROTOTYPES_BOOL) {
ord_dictionary =
isolate->factory()->NewOrderedNameDictionary(property_count);
} else {
dictionary = isolate->factory()->NewNameDictionary(property_count);
}
Handle<DescriptorArray> descs(
map->instance_descriptors(isolate, kRelaxedLoad), isolate);
for (InternalIndex i : InternalIndex::Range(real_size)) {
PropertyDetails details = descs->GetDetails(i);
Handle<Name> key(descs->GetKey(isolate, i), isolate);
Handle<Object> value;
if (details.location() == kField) {
FieldIndex index = FieldIndex::ForDescriptor(isolate, *map, i);
if (details.kind() == kData) {
if (object->IsUnboxedDoubleField(isolate, index)) {
double old_value = object->RawFastDoublePropertyAt(index);
value = isolate->factory()->NewHeapNumber(old_value);
} else {
value = handle(object->RawFastPropertyAt(isolate, index), isolate);
if (details.representation().IsDouble()) {
DCHECK(value->IsHeapNumber(isolate));
double old_value = Handle<HeapNumber>::cast(value)->value();
value = isolate->factory()->NewHeapNumber(old_value);
}
}
} else {
DCHECK_EQ(kAccessor, details.kind());
value = handle(object->RawFastPropertyAt(isolate, index), isolate);
}
} else {
DCHECK_EQ(kDescriptor, details.location());
value = handle(descs->GetStrongValue(isolate, i), isolate);
}
DCHECK(!value.is_null());
PropertyDetails d(details.kind(), details.attributes(),
PropertyCellType::kNoCell);
if (V8_DICT_MODE_PROTOTYPES_BOOL) {
ord_dictionary =
OrderedNameDictionary::Add(isolate, ord_dictionary, key, value, d)
.ToHandleChecked();
} else {
dictionary = NameDictionary::Add(isolate, dictionary, key, value, d);
}
}
if (!V8_DICT_MODE_PROTOTYPES_BOOL) {
// Copy the next enumeration index from instance descriptor.
dictionary->set_next_enumeration_index(real_size + 1);
}
// From here on we cannot fail and we shouldn't GC anymore.
DisallowHeapAllocation no_allocation;
Heap* heap = isolate->heap();
// Invalidate slots manually later in case the new map has in-object
// properties. If not, it is not possible to store an untagged value
// in a recorded slot.
heap->NotifyObjectLayoutChange(*object, no_allocation,
InvalidateRecordedSlots::kNo);
// Resize the object in the heap if necessary.
int old_instance_size = map->instance_size();
int new_instance_size = new_map->instance_size();
int instance_size_delta = old_instance_size - new_instance_size;
DCHECK_GE(instance_size_delta, 0);
if (instance_size_delta > 0) {
heap->CreateFillerObjectAt(object->address() + new_instance_size,
instance_size_delta, ClearRecordedSlots::kYes);
}
// We are storing the new map using release store after creating a filler for
// the left-over space to avoid races with the sweeper thread.
object->synchronized_set_map(*new_map);
if (V8_DICT_MODE_PROTOTYPES_BOOL) {
object->SetProperties(*ord_dictionary);
} else {
object->SetProperties(*dictionary);
}
// Ensure that in-object space of slow-mode object does not contain random
// garbage.
int inobject_properties = new_map->GetInObjectProperties();
if (inobject_properties) {
MemoryChunk* chunk = MemoryChunk::FromHeapObject(*object);
chunk->InvalidateRecordedSlots(*object);
for (int i = 0; i < inobject_properties; i++) {
FieldIndex index = FieldIndex::ForPropertyIndex(*new_map, i);
object->RawFastPropertyAtPut(index, Smi::zero());
}
}
isolate->counters()->props_to_dictionary()->Increment();
#ifdef DEBUG
if (FLAG_trace_normalization) {
StdoutStream os;
os << "Object properties have been normalized:\n";
object->Print(os);
}
#endif
}
} // namespace
void JSObject::MigrateToMap(Isolate* isolate, Handle<JSObject> object,
Handle<Map> new_map,
int expected_additional_properties) {
if (object->map(isolate) == *new_map) return;
Handle<Map> old_map(object->map(isolate), isolate);
NotifyMapChange(old_map, new_map, isolate);
if (old_map->is_dictionary_map()) {
// For slow-to-fast migrations JSObject::MigrateSlowToFast()
// must be used instead.
CHECK(new_map->is_dictionary_map());
// Slow-to-slow migration is trivial.
object->synchronized_set_map(*new_map);
} else if (!new_map->is_dictionary_map()) {
MigrateFastToFast(isolate, object, new_map);
if (old_map->is_prototype_map()) {
DCHECK(!old_map->is_stable());
DCHECK(new_map->is_stable());
DCHECK(new_map->owns_descriptors());
DCHECK(old_map->owns_descriptors());
// Transfer ownership to the new map. Keep the descriptor pointer of the
// old map intact because the concurrent marker might be iterating the
// object with the old map.
old_map->set_owns_descriptors(false);
DCHECK(old_map->is_abandoned_prototype_map());
// Ensure that no transition was inserted for prototype migrations.
DCHECK_EQ(0, TransitionsAccessor(isolate, old_map).NumberOfTransitions());
DCHECK(new_map->GetBackPointer(isolate).IsUndefined(isolate));
DCHECK(object->map(isolate) != *old_map);
}
} else {
MigrateFastToSlow(isolate, object, new_map, expected_additional_properties);
}
// Careful: Don't allocate here!
// For some callers of this method, |object| might be in an inconsistent
// state now: the new map might have a new elements_kind, but the object's
// elements pointer hasn't been updated yet. Callers will fix this, but in
// the meantime, (indirectly) calling JSObjectVerify() must be avoided.
// When adding code here, add a DisallowHeapAllocation too.
}
void JSObject::ForceSetPrototype(Handle<JSObject> object,
Handle<HeapObject> proto) {
// object.__proto__ = proto;
Isolate* isolate = object->GetIsolate();
Handle<Map> old_map = Handle<Map>(object->map(), isolate);
Handle<Map> new_map = Map::Copy(isolate, old_map, "ForceSetPrototype");
Map::SetPrototype(isolate, new_map, proto);
JSObject::MigrateToMap(isolate, object, new_map);
}
Maybe<bool> JSObject::SetPropertyWithInterceptor(
LookupIterator* it, Maybe<ShouldThrow> should_throw, Handle<Object> value) {
DCHECK_EQ(LookupIterator::INTERCEPTOR, it->state());
return SetPropertyWithInterceptorInternal(it, it->GetInterceptor(),
should_throw, value);
}
Handle<Map> JSObject::GetElementsTransitionMap(Handle<JSObject> object,
ElementsKind to_kind) {
Handle<Map> map(object->map(), object->GetIsolate());
return Map::TransitionElementsTo(object->GetIsolate(), map, to_kind);
}
// static
MaybeHandle<NativeContext> JSObject::GetFunctionRealm(Handle<JSObject> object) {
DCHECK(object->map().is_constructor());
DCHECK(!object->IsJSFunction());
return object->GetCreationContext();
}
void JSObject::AllocateStorageForMap(Handle<JSObject> object, Handle<Map> map) {
DCHECK(object->map().GetInObjectProperties() == map->GetInObjectProperties());
ElementsKind obj_kind = object->map().elements_kind();
ElementsKind map_kind = map->elements_kind();
if (map_kind != obj_kind) {
ElementsKind to_kind = GetMoreGeneralElementsKind(map_kind, obj_kind);
if (IsDictionaryElementsKind(obj_kind)) {
to_kind = obj_kind;
}
if (IsDictionaryElementsKind(to_kind)) {
NormalizeElements(object);
} else {
TransitionElementsKind(object, to_kind);
}
map = Map::ReconfigureElementsKind(object->GetIsolate(), map, to_kind);
}
int number_of_fields = map->NumberOfFields();
int inobject = map->GetInObjectProperties();
int unused = map->UnusedPropertyFields();
int total_size = number_of_fields + unused;
int external = total_size - inobject;
// Allocate mutable double boxes if necessary. It is always necessary if we
// have external properties, but is also necessary if we only have inobject
// properties but don't unbox double fields.
if (!FLAG_unbox_double_fields || external > 0) {
Isolate* isolate = object->GetIsolate();
Handle<DescriptorArray> descriptors(map->instance_descriptors(kRelaxedLoad),
isolate);
Handle<FixedArray> storage;
if (!FLAG_unbox_double_fields) {
storage = isolate->factory()->NewFixedArray(inobject);
}
Handle<PropertyArray> array =
isolate->factory()->NewPropertyArray(external);
for (InternalIndex i : map->IterateOwnDescriptors()) {
PropertyDetails details = descriptors->GetDetails(i);
Representation representation = details.representation();
if (!representation.IsDouble()) continue;
FieldIndex index = FieldIndex::ForDescriptor(*map, i);
if (map->IsUnboxedDoubleField(index)) continue;
auto box = isolate->factory()->NewHeapNumberWithHoleNaN();
if (index.is_inobject()) {
storage->set(index.property_index(), *box);
} else {
array->set(index.outobject_array_index(), *box);
}
}
object->SetProperties(*array);
if (!FLAG_unbox_double_fields) {
for (int i = 0; i < inobject; i++) {
FieldIndex index = FieldIndex::ForPropertyIndex(*map, i);
Object value = storage->get(i);
object->RawFastPropertyAtPut(index, value);
}
}
}
object->synchronized_set_map(*map);
}
void JSObject::MigrateInstance(Isolate* isolate, Handle<JSObject> object) {
Handle<Map> original_map(object->map(), isolate);
Handle<Map> map = Map::Update(isolate, original_map);
map->set_is_migration_target(true);
JSObject::MigrateToMap(isolate, object, map);
if (FLAG_trace_migration) {
object->PrintInstanceMigration(stdout, *original_map, *map);
}
#if VERIFY_HEAP
if (FLAG_verify_heap) {
object->JSObjectVerify(isolate);
}
#endif
}
// static
bool JSObject::TryMigrateInstance(Isolate* isolate, Handle<JSObject> object) {
DisallowDeoptimization no_deoptimization(isolate);
Handle<Map> original_map(object->map(), isolate);
Handle<Map> new_map;
if (!Map::TryUpdate(isolate, original_map).ToHandle(&new_map)) {
return false;
}
JSObject::MigrateToMap(isolate, object, new_map);
if (FLAG_trace_migration && *original_map != object->map()) {
object->PrintInstanceMigration(stdout, *original_map, object->map());
}
#if VERIFY_HEAP
if (FLAG_verify_heap) {
object->JSObjectVerify(isolate);
}
#endif
return true;
}
void JSObject::AddProperty(Isolate* isolate, Handle<JSObject> object,
Handle<Name> name, Handle<Object> value,
PropertyAttributes attributes) {
LookupIterator it(isolate, object, name, object,
LookupIterator::OWN_SKIP_INTERCEPTOR);
CHECK_NE(LookupIterator::ACCESS_CHECK, it.state());
#ifdef DEBUG
uint32_t index;
DCHECK(!object->IsJSProxy());
DCHECK(!name->AsArrayIndex(&index));
Maybe<PropertyAttributes> maybe = GetPropertyAttributes(&it);
DCHECK(maybe.IsJust());
DCHECK(!it.IsFound());
DCHECK(object->map().is_extensible() || name->IsPrivate());
#endif
CHECK(Object::AddDataProperty(&it, value, attributes,
Just(ShouldThrow::kThrowOnError),
StoreOrigin::kNamed)
.IsJust());
}
void JSObject::AddProperty(Isolate* isolate, Handle<JSObject> object,
const char* name, Handle<Object> value,
PropertyAttributes attributes) {
JSObject::AddProperty(isolate, object,
isolate->factory()->InternalizeUtf8String(name), value,
attributes);
}
// Reconfigures a property to a data property with attributes, even if it is not
// reconfigurable.
// Requires a LookupIterator that does not look at the prototype chain beyond
// hidden prototypes.
MaybeHandle<Object> JSObject::DefineOwnPropertyIgnoreAttributes(
LookupIterator* it, Handle<Object> value, PropertyAttributes attributes,
AccessorInfoHandling handling) {
MAYBE_RETURN_NULL(DefineOwnPropertyIgnoreAttributes(
it, value, attributes, Just(ShouldThrow::kThrowOnError), handling));
return value;
}
Maybe<bool> JSObject::DefineOwnPropertyIgnoreAttributes(
LookupIterator* it, Handle<Object> value, PropertyAttributes attributes,
Maybe<ShouldThrow> should_throw, AccessorInfoHandling handling) {
it->UpdateProtector();
Handle<JSObject> object = Handle<JSObject>::cast(it->GetReceiver());
for (; it->IsFound(); it->Next()) {
switch (it->state()) {
case LookupIterator::JSPROXY:
case LookupIterator::NOT_FOUND:
case LookupIterator::TRANSITION:
UNREACHABLE();
case LookupIterator::ACCESS_CHECK:
if (!it->HasAccess()) {
it->isolate()->ReportFailedAccessCheck(it->GetHolder<JSObject>());
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(it->isolate(), Nothing<bool>());
return Just(true);
}
break;
// If there's an interceptor, try to store the property with the
// interceptor.
// In case of success, the attributes will have been reset to the default
// attributes of the interceptor, rather than the incoming attributes.
//
// TODO(verwaest): JSProxy afterwards verify the attributes that the
// JSProxy claims it has, and verifies that they are compatible. If not,
// they throw. Here we should do the same.
case LookupIterator::INTERCEPTOR:
if (handling == DONT_FORCE_FIELD) {
Maybe<bool> result =
JSObject::SetPropertyWithInterceptor(it, should_throw, value);
if (result.IsNothing() || result.FromJust()) return result;
}
break;
case LookupIterator::ACCESSOR: {
Handle<Object> accessors = it->GetAccessors();
// Special handling for AccessorInfo, which behaves like a data
// property.
if (accessors->IsAccessorInfo() && handling == DONT_FORCE_FIELD) {
PropertyAttributes current_attributes = it->property_attributes();
// Ensure the context isn't changed after calling into accessors.
AssertNoContextChange ncc(it->isolate());
// Update the attributes before calling the setter. The setter may
// later change the shape of the property.
if (current_attributes != attributes) {
it->TransitionToAccessorPair(accessors, attributes);
}
return Object::SetPropertyWithAccessor(it, value, should_throw);
}
it->ReconfigureDataProperty(value, attributes);
return Just(true);
}
case LookupIterator::INTEGER_INDEXED_EXOTIC:
return Object::RedefineIncompatibleProperty(
it->isolate(), it->GetName(), value, should_throw);
case LookupIterator::DATA: {
// Regular property update if the attributes match.
if (it->property_attributes() == attributes) {
return Object::SetDataProperty(it, value);
}
// Special case: properties of typed arrays cannot be reconfigured to
// non-writable nor to non-enumerable.
if (it->IsElement() && object->HasTypedArrayElements()) {
return Object::RedefineIncompatibleProperty(
it->isolate(), it->GetName(), value, should_throw);
}
// Reconfigure the data property if the attributes mismatch.
it->ReconfigureDataProperty(value, attributes);
return Just(true);
}
}
}
return Object::AddDataProperty(it, value, attributes, should_throw,
StoreOrigin::kNamed);
}
MaybeHandle<Object> JSObject::SetOwnPropertyIgnoreAttributes(
Handle<JSObject> object, Handle<Name> name, Handle<Object> value,
PropertyAttributes attributes) {
DCHECK(!value->IsTheHole());
LookupIterator it(object->GetIsolate(), object, name, object,
LookupIterator::OWN);
return DefineOwnPropertyIgnoreAttributes(&it, value, attributes);
}
MaybeHandle<Object> JSObject::SetOwnElementIgnoreAttributes(
Handle<JSObject> object, size_t index, Handle<Object> value,
PropertyAttributes attributes) {
DCHECK(!object->IsJSTypedArray());
Isolate* isolate = object->GetIsolate();
LookupIterator it(isolate, object, index, object, LookupIterator::OWN);
return DefineOwnPropertyIgnoreAttributes(&it, value, attributes);
}
MaybeHandle<Object> JSObject::DefinePropertyOrElementIgnoreAttributes(
Handle<JSObject> object, Handle<Name> name, Handle<Object> value,
PropertyAttributes attributes) {
Isolate* isolate = object->GetIsolate();
LookupIterator::Key key(isolate, name);
LookupIterator it(isolate, object, key, object, LookupIterator::OWN);
return DefineOwnPropertyIgnoreAttributes(&it, value, attributes);
}
Maybe<PropertyAttributes> JSObject::GetPropertyAttributesWithInterceptor(
LookupIterator* it) {
return GetPropertyAttributesWithInterceptorInternal(it, it->GetInterceptor());
}
void JSObject::NormalizeProperties(Isolate* isolate, Handle<JSObject> object,
PropertyNormalizationMode mode,
int expected_additional_properties,
const char* reason) {
if (!object->HasFastProperties()) return;
Handle<Map> map(object->map(), isolate);
Handle<Map> new_map =
Map::Normalize(isolate, map, map->elements_kind(), mode, reason);
JSObject::MigrateToMap(isolate, object, new_map,
expected_additional_properties);
}
void JSObject::MigrateSlowToFast(Handle<JSObject> object,
int unused_property_fields,
const char* reason) {
if (object->HasFastProperties()) return;
DCHECK(!object->IsJSGlobalObject());
Isolate* isolate = object->GetIsolate();
Factory* factory = isolate->factory();
Handle<NameDictionary> dictionary;
Handle<OrderedNameDictionary> ord_dictionary;
int number_of_elements;
if (V8_DICT_MODE_PROTOTYPES_BOOL) {
ord_dictionary = handle(object->property_dictionary_ordered(), isolate);
number_of_elements = ord_dictionary->NumberOfElements();
} else {
dictionary = handle(object->property_dictionary(), isolate);
number_of_elements = dictionary->NumberOfElements();
}
// Make sure we preserve dictionary representation if there are too many
// descriptors.
if (number_of_elements > kMaxNumberOfDescriptors) return;
Handle<FixedArray> iteration_order;
int iteration_length;
if (V8_DICT_MODE_PROTOTYPES_BOOL) {
// |iteration_order| remains empty handle, we don't need it.
iteration_length = ord_dictionary->UsedCapacity();
} else {
iteration_order = NameDictionary::IterationIndices(isolate, dictionary);
iteration_length = dictionary->NumberOfElements();
}
int number_of_fields = 0;
// Compute the length of the instance descriptor.
ReadOnlyRoots roots(isolate);
for (int i = 0; i < iteration_length; i++) {
PropertyKind kind;
if (V8_DICT_MODE_PROTOTYPES_BOOL) {
InternalIndex index(i);
Object key = ord_dictionary->KeyAt(index);
if (!OrderedNameDictionary::IsKey(roots, key)) {
// Ignore deleted entries.
continue;
}
kind = ord_dictionary->DetailsAt(index).kind();
} else {
InternalIndex index(Smi::ToInt(iteration_order->get(i)));
DCHECK(dictionary->IsKey(roots, dictionary->KeyAt(isolate, index)));
kind = dictionary->DetailsAt(index).kind();
}
if (kind == kData) {
number_of_fields += 1;
}
}
Handle<Map> old_map(object->map(), isolate);
int inobject_props = old_map->GetInObjectProperties();
// Allocate new map.
Handle<Map> new_map = Map::CopyDropDescriptors(isolate, old_map);
// We should not only set this bit if we need to. We should not retain the
// old bit because turning a map into dictionary always sets this bit.
new_map->set_may_have_interesting_symbols(new_map->has_named_interceptor() ||
new_map->is_access_check_needed());
new_map->set_is_dictionary_map(false);
NotifyMapChange(old_map, new_map, isolate);
if (number_of_elements == 0) {
DisallowHeapAllocation no_gc;
DCHECK_LE(unused_property_fields, inobject_props);
// Transform the object.
new_map->SetInObjectUnusedPropertyFields(inobject_props);
object->synchronized_set_map(*new_map);
object->SetProperties(ReadOnlyRoots(isolate).empty_fixed_array());
// Check that it really works.
DCHECK(object->HasFastProperties());
if (FLAG_trace_maps) {
LOG(isolate, MapEvent("SlowToFast", old_map, new_map, reason));
}
return;
}
// Allocate the instance descriptor.
Handle<DescriptorArray> descriptors =
DescriptorArray::Allocate(isolate, number_of_elements, 0);
int number_of_allocated_fields =
number_of_fields + unused_property_fields - inobject_props;
if (number_of_allocated_fields < 0) {
// There is enough inobject space for all fields (including unused).
number_of_allocated_fields = 0;
unused_property_fields = inobject_props - number_of_fields;
}
// Allocate the property array for the fields.
Handle<PropertyArray> fields =
factory->NewPropertyArray(number_of_allocated_fields);
bool is_transitionable_elements_kind =
IsTransitionableFastElementsKind(old_map->elements_kind());
// Fill in the instance descriptor and the fields.
int current_offset = 0;
int descriptor_index = 0;
for (int i = 0; i < iteration_length; i++) {
Name k;
Object value;
PropertyDetails details = PropertyDetails::Empty();
if (V8_DICT_MODE_PROTOTYPES_BOOL) {
InternalIndex index(i);
Object key_obj = ord_dictionary->KeyAt(index);
if (!OrderedNameDictionary::IsKey(roots, key_obj)) {
continue;
}
k = Name::cast(key_obj);
value = ord_dictionary->ValueAt(index);
details = ord_dictionary->DetailsAt(index);
} else {
InternalIndex index(Smi::ToInt(iteration_order->get(i)));
k = dictionary->NameAt(index);
value = dictionary->ValueAt(index);
details = dictionary->DetailsAt(index);
}
// Dictionary keys are internalized upon insertion.
// TODO(jkummerow): Turn this into a DCHECK if it's not hit in the wild.
CHECK(k.IsUniqueName());
Handle<Name> key(k, isolate);
// Properly mark the {new_map} if the {key} is an "interesting symbol".
if (key->IsInterestingSymbol()) {
new_map->set_may_have_interesting_symbols(true);
}
DCHECK_EQ(kField, details.location());
DCHECK_EQ(PropertyConstness::kMutable, details.constness());
Descriptor d;
if (details.kind() == kData) {
// Ensure that we make constant field only when elements kind is not
// transitionable.
PropertyConstness constness = is_transitionable_elements_kind
? PropertyConstness::kMutable
: PropertyConstness::kConst;
d = Descriptor::DataField(
key, current_offset, details.attributes(), constness,
// TODO(verwaest): value->OptimalRepresentation();
Representation::Tagged(), MaybeObjectHandle(FieldType::Any(isolate)));
} else {
DCHECK_EQ(kAccessor, details.kind());
d = Descriptor::AccessorConstant(key, handle(value, isolate),
details.attributes());
}
details = d.GetDetails();
if (details.location() == kField) {
if (current_offset < inobject_props) {
object->InObjectPropertyAtPut(current_offset, value,
UPDATE_WRITE_BARRIER);
} else {
int offset = current_offset - inobject_props;
fields->set(offset, value);
}
current_offset += details.field_width_in_words();
}
descriptors->Set(InternalIndex(descriptor_index++), &d);
}
DCHECK_EQ(current_offset, number_of_fields);
DCHECK_EQ(descriptor_index, number_of_elements);
descriptors->Sort();
Handle<LayoutDescriptor> layout_descriptor = LayoutDescriptor::New(
isolate, new_map, descriptors, descriptors->number_of_descriptors());
DisallowHeapAllocation no_gc;
new_map->InitializeDescriptors(isolate, *descriptors, *layout_descriptor);
if (number_of_allocated_fields == 0) {
new_map->SetInObjectUnusedPropertyFields(unused_property_fields);
} else {
new_map->SetOutOfObjectUnusedPropertyFields(unused_property_fields);
}
if (FLAG_trace_maps) {
LOG(isolate, MapEvent("SlowToFast", old_map, new_map, reason));
}
// Transform the object.
object->synchronized_set_map(*new_map);
object->SetProperties(*fields);
DCHECK(object->IsJSObject());
// Check that it really works.
DCHECK(object->HasFastProperties());
}
void JSObject::RequireSlowElements(NumberDictionary dictionary) {
DCHECK_NE(dictionary,
ReadOnlyRoots(GetIsolate()).empty_slow_element_dictionary());
if (dictionary.requires_slow_elements()) return;
dictionary.set_requires_slow_elements();
if (map().is_prototype_map()) {
// If this object is a prototype (the callee will check), invalidate any
// prototype chains involving it.
InvalidatePrototypeChains(map());
}
}
Handle<NumberDictionary> JSObject::NormalizeElements(Handle<JSObject> object) {
DCHECK(!object->HasTypedArrayElements());
Isolate* isolate = object->GetIsolate();
bool is_sloppy_arguments = object->HasSloppyArgumentsElements();
{
DisallowHeapAllocation no_gc;
FixedArrayBase elements = object->elements();
if (is_sloppy_arguments) {
elements = SloppyArgumentsElements::cast(elements).arguments();
}
if (elements.IsNumberDictionary()) {
return handle(NumberDictionary::cast(elements), isolate);
}
}
DCHECK(object->HasSmiOrObjectElements() || object->HasDoubleElements() ||
object->HasFastArgumentsElements() ||
object->HasFastStringWrapperElements() ||
object->HasSealedElements() || object->HasNonextensibleElements());
Handle<NumberDictionary> dictionary =
object->GetElementsAccessor()->Normalize(object);
// Switch to using the dictionary as the backing storage for elements.
ElementsKind target_kind = is_sloppy_arguments
? SLOW_SLOPPY_ARGUMENTS_ELEMENTS
: object->HasFastStringWrapperElements()
? SLOW_STRING_WRAPPER_ELEMENTS
: DICTIONARY_ELEMENTS;
Handle<Map> new_map = JSObject::GetElementsTransitionMap(object, target_kind);
// Set the new map first to satify the elements type assert in set_elements().
JSObject::MigrateToMap(isolate, object, new_map);
if (is_sloppy_arguments) {
SloppyArgumentsElements::cast(object->elements())
.set_arguments(*dictionary);
} else {
object->set_elements(*dictionary);
}
isolate->counters()->elements_to_dictionary()->Increment();
#ifdef DEBUG
if (FLAG_trace_normalization) {
StdoutStream os;
os << "Object elements have been normalized:\n";
object->Print(os);
}
#endif
DCHECK(object->HasDictionaryElements() ||
object->HasSlowArgumentsElements() ||
object->HasSlowStringWrapperElements());
return dictionary;
}
Maybe<bool> JSObject::DeletePropertyWithInterceptor(LookupIterator* it,
ShouldThrow should_throw) {
Isolate* isolate = it->isolate();
// Make sure that the top context does not change when doing callbacks or
// interceptor calls.
AssertNoContextChange ncc(isolate);
DCHECK_EQ(LookupIterator::INTERCEPTOR, it->state());
Handle<InterceptorInfo> interceptor(it->GetInterceptor());
if (interceptor->deleter().IsUndefined(isolate)) return Nothing<bool>();
Handle<JSObject> holder = it->GetHolder<JSObject>();
Handle<Object> receiver = it->GetReceiver();
if (!receiver->IsJSReceiver()) {
ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, receiver,
Object::ConvertReceiver(isolate, receiver),
Nothing<bool>());
}
PropertyCallbackArguments args(isolate, interceptor->data(), *receiver,
*holder, Just(should_throw));
Handle<Object> result;
if (it->IsElement(*holder)) {
result = args.CallIndexedDeleter(interceptor, it->array_index());
} else {
result = args.CallNamedDeleter(interceptor, it->name());
}
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, Nothing<bool>());
if (result.is_null()) return Nothing<bool>();
DCHECK(result->IsBoolean());
// Rebox CustomArguments::kReturnValueOffset before returning.
return Just(result->IsTrue(isolate));
}
Maybe<bool> JSObject::CreateDataProperty(LookupIterator* it,
Handle<Object> value,
Maybe<ShouldThrow> should_throw) {
DCHECK(it->GetReceiver()->IsJSObject());
MAYBE_RETURN(JSReceiver::GetPropertyAttributes(it), Nothing<bool>());
Handle<JSReceiver> receiver = Handle<JSReceiver>::cast(it->GetReceiver());
Isolate* isolate = receiver->GetIsolate();
if (it->IsFound()) {
Maybe<PropertyAttributes> attributes = GetPropertyAttributes(it);
MAYBE_RETURN(attributes, Nothing<bool>());
if ((attributes.FromJust() & DONT_DELETE) != 0) {
RETURN_FAILURE(
isolate, GetShouldThrow(isolate, should_throw),
NewTypeError(MessageTemplate::kRedefineDisallowed, it->GetName()));
}
} else {
if (!JSObject::IsExtensible(Handle<JSObject>::cast(it->GetReceiver()))) {
RETURN_FAILURE(
isolate, GetShouldThrow(isolate, should_throw),
NewTypeError(MessageTemplate::kDefineDisallowed, it->GetName()));
}
}
RETURN_ON_EXCEPTION_VALUE(it->isolate(),
DefineOwnPropertyIgnoreAttributes(it, value, NONE),
Nothing<bool>());
return Just(true);
}
namespace {
template <typename Dictionary>
bool TestDictionaryPropertiesIntegrityLevel(Dictionary dict,
ReadOnlyRoots roots,
PropertyAttributes level) {
DCHECK(level == SEALED || level == FROZEN);
for (InternalIndex i : dict.IterateEntries()) {
Object key;
if (!dict.ToKey(roots, i, &key)) continue;
if (key.FilterKey(ALL_PROPERTIES)) continue;
PropertyDetails details = dict.DetailsAt(i);
if (details.IsConfigurable()) return false;
if (level == FROZEN && details.kind() == kData && !details.IsReadOnly()) {
return false;
}
}
return true;
}
bool TestFastPropertiesIntegrityLevel(Map map, PropertyAttributes level) {
DCHECK(level == SEALED || level == FROZEN);
DCHECK(!map.IsCustomElementsReceiverMap());
DCHECK(!map.is_dictionary_map());
DescriptorArray descriptors = map.instance_descriptors(kRelaxedLoad);
for (InternalIndex i : map.IterateOwnDescriptors()) {
if (descriptors.GetKey(i).IsPrivate()) continue;
PropertyDetails details = descriptors.GetDetails(i);
if (details.IsConfigurable()) return false;
if (level == FROZEN && details.kind() == kData && !details.IsReadOnly()) {
return false;
}
}
return true;
}
bool TestPropertiesIntegrityLevel(JSObject object, PropertyAttributes level) {
DCHECK(!object.map().IsCustomElementsReceiverMap());
if (object.HasFastProperties()) {
return TestFastPropertiesIntegrityLevel(object.map(), level);
}
if (V8_DICT_MODE_PROTOTYPES_BOOL) {
return TestDictionaryPropertiesIntegrityLevel(
object.property_dictionary_ordered(), object.GetReadOnlyRoots(), level);
} else {
return TestDictionaryPropertiesIntegrityLevel(
object.property_dictionary(), object.GetReadOnlyRoots(), level);
}
}
bool TestElementsIntegrityLevel(JSObject object, PropertyAttributes level) {
DCHECK(!object.HasSloppyArgumentsElements());
ElementsKind kind = object.GetElementsKind();
if (IsDictionaryElementsKind(kind)) {
return TestDictionaryPropertiesIntegrityLevel(
NumberDictionary::cast(object.elements()), object.GetReadOnlyRoots(),
level);
}
if (IsTypedArrayElementsKind(kind)) {
if (level == FROZEN && JSArrayBufferView::cast(object).byte_length() > 0) {
return false; // TypedArrays with elements can't be frozen.
}
return TestPropertiesIntegrityLevel(object, level);
}
if (IsFrozenElementsKind(kind)) return true;
if (IsSealedElementsKind(kind) && level != FROZEN) return true;
if (IsNonextensibleElementsKind(kind) && level == NONE) return true;
ElementsAccessor* accessor = ElementsAccessor::ForKind(kind);
// Only DICTIONARY_ELEMENTS and SLOW_SLOPPY_ARGUMENTS_ELEMENTS have
// PropertyAttributes so just test if empty
return accessor->NumberOfElements(object) == 0;
}
bool FastTestIntegrityLevel(JSObject object, PropertyAttributes level) {
DCHECK(!object.map().IsCustomElementsReceiverMap());
return !object.map().is_extensible() &&
TestElementsIntegrityLevel(object, level) &&
TestPropertiesIntegrityLevel(object, level);
}
} // namespace
Maybe<bool> JSObject::TestIntegrityLevel(Handle<JSObject> object,
IntegrityLevel level) {
if (!object->map().IsCustomElementsReceiverMap() &&
!object->HasSloppyArgumentsElements()) {
return Just(FastTestIntegrityLevel(*object, level));
}
return GenericTestIntegrityLevel(Handle<JSReceiver>::cast(object), level);
}
Maybe<bool> JSObject::PreventExtensions(Handle<JSObject> object,
ShouldThrow should_throw) {
Isolate* isolate = object->GetIsolate();
if (!object->HasSloppyArgumentsElements()) {
return PreventExtensionsWithTransition<NONE>(object, should_throw);
}
if (object->IsAccessCheckNeeded() &&
!isolate->MayAccess(handle(isolate->context(), isolate), object)) {
isolate->ReportFailedAccessCheck(object);
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, Nothing<bool>());
RETURN_FAILURE(isolate, should_throw,
NewTypeError(MessageTemplate::kNoAccess));
}
if (!object->map().is_extensible()) return Just(true);
if (object->IsJSGlobalProxy()) {
PrototypeIterator iter(isolate, object);
if (iter.IsAtEnd()) return Just(true);
DCHECK(PrototypeIterator::GetCurrent(iter)->IsJSGlobalObject());
return PreventExtensions(PrototypeIterator::GetCurrent<JSObject>(iter),
should_throw);
}
if (object->map().has_named_interceptor() ||
object->map().has_indexed_interceptor()) {
RETURN_FAILURE(isolate, should_throw,
NewTypeError(MessageTemplate::kCannotPreventExt));
}
if (!object->HasTypedArrayElements()) {
// If there are fast elements we normalize.
Handle<NumberDictionary> dictionary = NormalizeElements(object);
DCHECK(object->HasDictionaryElements() ||
object->HasSlowArgumentsElements());
// Make sure that we never go back to fast case.
if (*dictionary != ReadOnlyRoots(isolate).empty_slow_element_dictionary()) {
object->RequireSlowElements(*dictionary);
}
}
// Do a map transition, other objects with this map may still
// be extensible.
// TODO(adamk): Extend the NormalizedMapCache to handle non-extensible maps.
Handle<Map> new_map =
Map::Copy(isolate, handle(object->map(), isolate), "PreventExtensions");
new_map->set_is_extensible(false);
JSObject::MigrateToMap(isolate, object, new_map);
DCHECK(!object->map().is_extensible());
return Just(true);
}
bool JSObject::IsExtensible(Handle<JSObject> object) {
Isolate* isolate = object->GetIsolate();
if (object->IsAccessCheckNeeded() &&
!isolate->MayAccess(handle(isolate->context(), isolate), object)) {
return true;
}
if (object->IsJSGlobalProxy()) {
PrototypeIterator iter(isolate, *object);
if (iter.IsAtEnd()) return false;
DCHECK(iter.GetCurrent().IsJSGlobalObject());
return iter.GetCurrent<JSObject>().map().is_extensible();
}
return object->map().is_extensible();
}
template <typename Dictionary>
void JSObject::ApplyAttributesToDictionary(
Isolate* isolate, ReadOnlyRoots roots, Handle<Dictionary> dictionary,
const PropertyAttributes attributes) {
for (InternalIndex i : dictionary->IterateEntries()) {
Object k;
if (!dictionary->ToKey(roots, i, &k)) continue;
if (k.FilterKey(ALL_PROPERTIES)) continue;
PropertyDetails details = dictionary->DetailsAt(i);
int attrs = attributes;
// READ_ONLY is an invalid attribute for JS setters/getters.
if ((attributes & READ_ONLY) && details.kind() == kAccessor) {
Object v = dictionary->ValueAt(i);
if (v.IsAccessorPair()) attrs &= ~READ_ONLY;
}
details = details.CopyAddAttributes(static_cast<PropertyAttributes>(attrs));
dictionary->DetailsAtPut(i, details);
}
}
template void JSObject::ApplyAttributesToDictionary(
Isolate* isolate, ReadOnlyRoots roots, Handle<NumberDictionary> dictionary,
const PropertyAttributes attributes);
Handle<NumberDictionary> CreateElementDictionary(Isolate* isolate,
Handle<JSObject> object) {
Handle<NumberDictionary> new_element_dictionary;
if (!object->HasTypedArrayElements() && !object->HasDictionaryElements() &&
!object->HasSlowStringWrapperElements()) {
int length = object->IsJSArray()
? Smi::ToInt(Handle<JSArray>::cast(object)->length())
: object->elements().length();
new_element_dictionary =
length == 0 ? isolate->factory()->empty_slow_element_dictionary()
: object->GetElementsAccessor()->Normalize(object);
}
return new_element_dictionary;
}
template <PropertyAttributes attrs>
Maybe<bool> JSObject::PreventExtensionsWithTransition(
Handle<JSObject> object, ShouldThrow should_throw) {
STATIC_ASSERT(attrs == NONE || attrs == SEALED || attrs == FROZEN);
// Sealing/freezing sloppy arguments or namespace objects should be handled
// elsewhere.
DCHECK(!object->HasSloppyArgumentsElements());
DCHECK_IMPLIES(object->IsJSModuleNamespace(), attrs == NONE);
Isolate* isolate = object->GetIsolate();
if (object->IsAccessCheckNeeded() &&
!isolate->MayAccess(handle(isolate->context(), isolate), object)) {
isolate->ReportFailedAccessCheck(object);
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, Nothing<bool>());
RETURN_FAILURE(isolate, should_throw,
NewTypeError(MessageTemplate::kNoAccess));
}
if (attrs == NONE && !object->map().is_extensible()) return Just(true);
{
ElementsKind old_elements_kind = object->map().elements_kind();
if (IsFrozenElementsKind(old_elements_kind)) return Just(true);
if (attrs != FROZEN && IsSealedElementsKind(old_elements_kind))
return Just(true);
}
if (object->IsJSGlobalProxy()) {
PrototypeIterator iter(isolate, object);
if (iter.IsAtEnd()) return Just(true);
DCHECK(PrototypeIterator::GetCurrent(iter)->IsJSGlobalObject());
return PreventExtensionsWithTransition<attrs>(
PrototypeIterator::GetCurrent<JSObject>(iter), should_throw);
}
if (object->map().has_named_interceptor() ||
object->map().has_indexed_interceptor()) {
MessageTemplate message = MessageTemplate::kNone;
switch (attrs) {
case NONE:
message = MessageTemplate::kCannotPreventExt;
break;
case SEALED:
message = MessageTemplate::kCannotSeal;
break;
case FROZEN:
message = MessageTemplate::kCannotFreeze;
break;
}
RETURN_FAILURE(isolate, should_throw, NewTypeError(message));
}
Handle<Symbol> transition_marker;
if (attrs == NONE) {
transition_marker = isolate->factory()->nonextensible_symbol();
} else if (attrs == SEALED) {
transition_marker = isolate->factory()->sealed_symbol();
} else {
DCHECK(attrs == FROZEN);
transition_marker = isolate->factory()->frozen_symbol();
}
// Currently, there are only have sealed/frozen Object element kinds and
// Map::MigrateToMap doesn't handle properties' attributes reconfiguring and
// elements kind change in one go. If seal or freeze with Smi or Double
// elements kind, we will transition to Object elements kind first to make
// sure of valid element access.
if (FLAG_enable_sealed_frozen_elements_kind) {
switch (object->map().elements_kind()) {
case PACKED_SMI_ELEMENTS:
case PACKED_DOUBLE_ELEMENTS:
JSObject::TransitionElementsKind(object, PACKED_ELEMENTS);
break;
case HOLEY_SMI_ELEMENTS:
case HOLEY_DOUBLE_ELEMENTS:
JSObject::TransitionElementsKind(object, HOLEY_ELEMENTS);
break;
default:
break;
}
}
// Make sure we only use this element dictionary in case we can't transition
// to sealed, frozen elements kind.
Handle<NumberDictionary> new_element_dictionary;
Handle<Map> old_map(object->map(), isolate);
old_map = Map::Update(isolate, old_map);
TransitionsAccessor transitions(isolate, old_map);
Map transition = transitions.SearchSpecial(*transition_marker);
if (!transition.is_null()) {
Handle<Map> transition_map(transition, isolate);
DCHECK(transition_map->has_dictionary_elements() ||
transition_map->has_typed_array_elements() ||
transition_map->elements_kind() == SLOW_STRING_WRAPPER_ELEMENTS ||
transition_map->has_any_nonextensible_elements());
DCHECK(!transition_map->is_extensible());
if (!transition_map->has_any_nonextensible_elements()) {
new_element_dictionary = CreateElementDictionary(isolate, object);
}
JSObject::MigrateToMap(isolate, object, transition_map);
} else if (transitions.CanHaveMoreTransitions()) {
// Create a new descriptor array with the appropriate property attributes
Handle<Map> new_map = Map::CopyForPreventExtensions(
isolate, old_map, attrs, transition_marker, "CopyForPreventExtensions");
if (!new_map->has_any_nonextensible_elements()) {
new_element_dictionary = CreateElementDictionary(isolate, object);
}
JSObject::MigrateToMap(isolate, object, new_map);
} else {
DCHECK(old_map->is_dictionary_map() || !old_map->is_prototype_map());
// Slow path: need to normalize properties for safety
NormalizeProperties(isolate, object, CLEAR_INOBJECT_PROPERTIES, 0,
"SlowPreventExtensions");
// Create a new map, since other objects with this map may be extensible.
// TODO(adamk): Extend the NormalizedMapCache to handle non-extensible maps.
Handle<Map> new_map = Map::Copy(isolate, handle(object->map(), isolate),
"SlowCopyForPreventExtensions");
new_map->set_is_extensible(false);
new_element_dictionary = CreateElementDictionary(isolate, object);
if (!new_element_dictionary.is_null()) {
ElementsKind new_kind =
IsStringWrapperElementsKind(old_map->elements_kind())
? SLOW_STRING_WRAPPER_ELEMENTS
: DICTIONARY_ELEMENTS;
new_map->set_elements_kind(new_kind);
}
JSObject::MigrateToMap(isolate, object, new_map);
if (attrs != NONE) {
ReadOnlyRoots roots(isolate);
if (object->IsJSGlobalObject()) {
Handle<GlobalDictionary> dictionary(
JSGlobalObject::cast(*object).global_dictionary(), isolate);
JSObject::ApplyAttributesToDictionary(isolate, roots, dictionary,
attrs);
} else if (V8_DICT_MODE_PROTOTYPES_BOOL) {
Handle<OrderedNameDictionary> dictionary(
object->property_dictionary_ordered(), isolate);
JSObject::ApplyAttributesToDictionary(isolate, roots, dictionary,
attrs);
} else {
Handle<NameDictionary> dictionary(object->property_dictionary(),
isolate);
JSObject::ApplyAttributesToDictionary(isolate, roots, dictionary,
attrs);
}
}
}
if (object->map().has_any_nonextensible_elements()) {
DCHECK(new_element_dictionary.is_null());
return Just(true);
}
// Both seal and preventExtensions always go through without modifications to
// typed array elements. Freeze works only if there are no actual elements.
if (object->HasTypedArrayElements()) {
if (attrs == FROZEN && JSArrayBufferView::cast(*object).byte_length() > 0) {
isolate->Throw(*isolate->factory()->NewTypeError(
MessageTemplate::kCannotFreezeArrayBufferView));
return Nothing<bool>();
}
return Just(true);
}
DCHECK(object->map().has_dictionary_elements() ||
object->map().elements_kind() == SLOW_STRING_WRAPPER_ELEMENTS);
if (!new_element_dictionary.is_null()) {
object->set_elements(*new_element_dictionary);
}
if (object->elements() !=
ReadOnlyRoots(isolate).empty_slow_element_dictionary()) {
Handle<NumberDictionary> dictionary(object->element_dictionary(), isolate);
// Make sure we never go back to the fast case
object->RequireSlowElements(*dictionary);
if (attrs != NONE) {
JSObject::ApplyAttributesToDictionary(isolate, ReadOnlyRoots(isolate),
dictionary, attrs);
}
}
return Just(true);
}
Handle<Object> JSObject::FastPropertyAt(Handle<JSObject> object,
Representation representation,
FieldIndex index) {
Isolate* isolate = object->GetIsolate();
if (object->IsUnboxedDoubleField(index)) {
DCHECK(representation.IsDouble());
double value = object->RawFastDoublePropertyAt(index);
return isolate->factory()->NewHeapNumber(value);
}
Handle<Object> raw_value(object->RawFastPropertyAt(index), isolate);
return Object::WrapForRead(isolate, raw_value, representation);
}
// TODO(cbruni/jkummerow): Consider moving this into elements.cc.
bool JSObject::HasEnumerableElements() {
// TODO(cbruni): cleanup
JSObject object = *this;
switch (object.GetElementsKind()) {
case PACKED_SMI_ELEMENTS:
case PACKED_ELEMENTS:
case PACKED_FROZEN_ELEMENTS:
case PACKED_SEALED_ELEMENTS:
case PACKED_NONEXTENSIBLE_ELEMENTS:
case PACKED_DOUBLE_ELEMENTS: {
int length = object.IsJSArray()
? Smi::ToInt(JSArray::cast(object).length())
: object.elements().length();
return length > 0;
}
case HOLEY_SMI_ELEMENTS:
case HOLEY_FROZEN_ELEMENTS:
case HOLEY_SEALED_ELEMENTS:
case HOLEY_NONEXTENSIBLE_ELEMENTS:
case HOLEY_ELEMENTS: {
FixedArray elements = FixedArray::cast(object.elements());
int length = object.IsJSArray()
? Smi::ToInt(JSArray::cast(object).length())
: elements.length();
Isolate* isolate = GetIsolate();
for (int i = 0; i < length; i++) {
if (!elements.is_the_hole(isolate, i)) return true;
}
return false;
}
case HOLEY_DOUBLE_ELEMENTS: {
int length = object.IsJSArray()
? Smi::ToInt(JSArray::cast(object).length())
: object.elements().length();
// Zero-length arrays would use the empty FixedArray...
if (length == 0) return false;
// ...so only cast to FixedDoubleArray otherwise.
FixedDoubleArray elements = FixedDoubleArray::cast(object.elements());
for (int i = 0; i < length; i++) {
if (!elements.is_the_hole(i)) return true;
}
return false;
}
#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) case TYPE##_ELEMENTS:
TYPED_ARRAYS(TYPED_ARRAY_CASE)
#undef TYPED_ARRAY_CASE
{
size_t length = JSTypedArray::cast(object).length();
return length > 0;
}
case DICTIONARY_ELEMENTS: {
NumberDictionary elements = NumberDictionary::cast(object.elements());
return elements.NumberOfEnumerableProperties() > 0;
}
case FAST_SLOPPY_ARGUMENTS_ELEMENTS:
case SLOW_SLOPPY_ARGUMENTS_ELEMENTS:
// We're approximating non-empty arguments objects here.
return true;
case FAST_STRING_WRAPPER_ELEMENTS:
case SLOW_STRING_WRAPPER_ELEMENTS:
if (String::cast(JSPrimitiveWrapper::cast(object).value()).length() > 0) {
return true;
}
return object.elements().length() > 0;
case NO_ELEMENTS:
return false;
}
UNREACHABLE();
}
MaybeHandle<Object> JSObject::DefineAccessor(Handle<JSObject> object,
Handle<Name> name,
Handle<Object> getter,
Handle<Object> setter,
PropertyAttributes attributes) {
Isolate* isolate = object->GetIsolate();
LookupIterator::Key key(isolate, name);
LookupIterator it(isolate, object, key, LookupIterator::OWN_SKIP_INTERCEPTOR);
return DefineAccessor(&it, getter, setter, attributes);
}
MaybeHandle<Object> JSObject::DefineAccessor(LookupIterator* it,
Handle<Object> getter,
Handle<Object> setter,
PropertyAttributes attributes) {
Isolate* isolate = it->isolate();
it->UpdateProtector();
if (it->state() == LookupIterator::ACCESS_CHECK) {
if (!it->HasAccess()) {
isolate->ReportFailedAccessCheck(it->GetHolder<JSObject>());
RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION(isolate, Object);
return isolate->factory()->undefined_value();
}
it->Next();
}
Handle<JSObject> object = Handle<JSObject>::cast(it->GetReceiver());
// Ignore accessors on typed arrays.
if (it->IsElement() && object->HasTypedArrayElements()) {
return it->factory()->undefined_value();
}
DCHECK(getter->IsCallable() || getter->IsUndefined(isolate) ||
getter->IsNull(isolate) || getter->IsFunctionTemplateInfo());
DCHECK(setter->IsCallable() || setter->IsUndefined(isolate) ||
setter->IsNull(isolate) || setter->IsFunctionTemplateInfo());
it->TransitionToAccessorProperty(getter, setter, attributes);
return isolate->factory()->undefined_value();
}
MaybeHandle<Object> JSObject::SetAccessor(Handle<JSObject> object,
Handle<Name> name,
Handle<AccessorInfo> info,
PropertyAttributes attributes) {
Isolate* isolate = object->GetIsolate();
LookupIterator::Key key(isolate, name);
LookupIterator it(isolate, object, key, LookupIterator::OWN_SKIP_INTERCEPTOR);
// Duplicate ACCESS_CHECK outside of GetPropertyAttributes for the case that
// the FailedAccessCheckCallbackFunction doesn't throw an exception.
//
// TODO(verwaest): Force throw an exception if the callback doesn't, so we can
// remove reliance on default return values.
if (it.state() == LookupIterator::ACCESS_CHECK) {
if (!it.HasAccess()) {
isolate->ReportFailedAccessCheck(object);
RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION(isolate, Object);
return it.factory()->undefined_value();
}
it.Next();
}
// Ignore accessors on typed arrays.
if (it.IsElement() && object->HasTypedArrayElements()) {
return it.factory()->undefined_value();
}
CHECK(GetPropertyAttributes(&it).IsJust());
// ES5 forbids turning a property into an accessor if it's not
// configurable. See 8.6.1 (Table 5).
if (it.IsFound() && !it.IsConfigurable()) {
return it.factory()->undefined_value();
}
it.TransitionToAccessorPair(info, attributes);
return object;
}
Object JSObject::SlowReverseLookup(Object value) {
if (HasFastProperties()) {
DescriptorArray descs = map().instance_descriptors(kRelaxedLoad);
bool value_is_number = value.IsNumber();
for (InternalIndex i : map().IterateOwnDescriptors()) {
PropertyDetails details = descs.GetDetails(i);
if (details.location() == kField) {
DCHECK_EQ(kData, details.kind());
FieldIndex field_index = FieldIndex::ForDescriptor(map(), i);
if (IsUnboxedDoubleField(field_index)) {
if (value_is_number) {
double property = RawFastDoublePropertyAt(field_index);
if (property == value.Number()) {
return descs.GetKey(i);
}
}
} else {
Object property = RawFastPropertyAt(field_index);
if (field_index.is_double()) {
DCHECK(property.IsHeapNumber());
if (value_is_number && property.Number() == value.Number()) {
return descs.GetKey(i);
}
} else if (property == value) {
return descs.GetKey(i);
}
}
} else {
DCHECK_EQ(kDescriptor, details.location());
if (details.kind() == kData) {
if (descs.GetStrongValue(i) == value) {
return descs.GetKey(i);
}
}
}
}
return GetReadOnlyRoots().undefined_value();
} else if (IsJSGlobalObject()) {
return JSGlobalObject::cast(*this).global_dictionary().SlowReverseLookup(
value);
} else if (V8_DICT_MODE_PROTOTYPES_BOOL) {
return property_dictionary_ordered().SlowReverseLookup(GetIsolate(), value);
} else {
return property_dictionary().SlowReverseLookup(value);
}
}
void JSObject::PrototypeRegistryCompactionCallback(HeapObject value,
int old_index,
int new_index) {
DCHECK(value.IsMap() && Map::cast(value).is_prototype_map());
Map map = Map::cast(value);
DCHECK(map.prototype_info().IsPrototypeInfo());
PrototypeInfo proto_info = PrototypeInfo::cast(map.prototype_info());
DCHECK_EQ(old_index, proto_info.registry_slot());
proto_info.set_registry_slot(new_index);
}
// static
void JSObject::MakePrototypesFast(Handle<Object> receiver,
WhereToStart where_to_start,
Isolate* isolate) {
if (!receiver->IsJSReceiver()) return;
for (PrototypeIterator iter(isolate, Handle<JSReceiver>::cast(receiver),
where_to_start);
!iter.IsAtEnd(); iter.Advance()) {
Handle<Object> current = PrototypeIterator::GetCurrent(iter);
if (!current->IsJSObject()) return;
Handle<JSObject> current_obj = Handle<JSObject>::cast(current);
Map current_map = current_obj->map();
if (current_map.is_prototype_map()) {
// If the map is already marked as should be fast, we're done. Its
// prototypes will have been marked already as well.
if (current_map.should_be_fast_prototype_map()) return;
Handle<Map> map(current_map, isolate);
Map::SetShouldBeFastPrototypeMap(map, true, isolate);
JSObject::OptimizeAsPrototype(current_obj);
}
}
}
static bool PrototypeBenefitsFromNormalization(Handle<JSObject> object) {
DisallowHeapAllocation no_gc;
if (!object->HasFastProperties()) return false;
if (object->IsJSGlobalProxy()) return false;
if (object->GetIsolate()->bootstrapper()->IsActive()) return false;
return !object->map().is_prototype_map() ||
!object->map().should_be_fast_prototype_map();
}
// static
void JSObject::OptimizeAsPrototype(Handle<JSObject> object,
bool enable_setup_mode) {
Isolate* isolate = object->GetIsolate();
if (object->IsJSGlobalObject()) return;
if (enable_setup_mode && PrototypeBenefitsFromNormalization(object)) {
// First normalize to ensure all JSFunctions are DATA_CONSTANT.
JSObject::NormalizeProperties(isolate, object, KEEP_INOBJECT_PROPERTIES, 0,
"NormalizeAsPrototype");
}
if (object->map().is_prototype_map()) {
if (object->map().should_be_fast_prototype_map() &&
!object->HasFastProperties()) {
JSObject::MigrateSlowToFast(object, 0, "OptimizeAsPrototype");
}
} else {
Handle<Map> new_map =
Map::Copy(isolate, handle(object->map(), isolate), "CopyAsPrototype");
JSObject::MigrateToMap(isolate, object, new_map);
object->map().set_is_prototype_map(true);
// Replace the pointer to the exact constructor with the Object function
// from the same context if undetectable from JS. This is to avoid keeping
// memory alive unnecessarily.
Object maybe_constructor = object->map().GetConstructor();
if (maybe_constructor.IsJSFunction()) {
JSFunction constructor = JSFunction::cast(maybe_constructor);
if (!constructor.shared().IsApiFunction()) {
Context context = constructor.context().native_context();
JSFunction object_function = context.object_function();
object->map().SetConstructor(object_function);
}
}
}
}
// static
void JSObject::ReoptimizeIfPrototype(Handle<JSObject> object) {
if (!object->map().is_prototype_map()) return;
if (!object->map().should_be_fast_prototype_map()) return;
OptimizeAsPrototype(object);
}
// static
void JSObject::LazyRegisterPrototypeUser(Handle<Map> user, Isolate* isolate) {
// Contract: In line with InvalidatePrototypeChains()'s requirements,
// leaf maps don't need to register as users, only prototypes do.
DCHECK(user->is_prototype_map());
Handle<Map> current_user = user;
Handle<PrototypeInfo> current_user_info =
Map::GetOrCreatePrototypeInfo(user, isolate);
for (PrototypeIterator iter(isolate, user); !iter.IsAtEnd(); iter.Advance()) {
// Walk up the prototype chain as far as links haven't been registered yet.
if (current_user_info->registry_slot() != PrototypeInfo::UNREGISTERED) {
break;
}
Handle<Object> maybe_proto = PrototypeIterator::GetCurrent(iter);
// Proxies on the prototype chain are not supported. They make it
// impossible to make any assumptions about the prototype chain anyway.
if (maybe_proto->IsJSProxy()) return;
Handle<JSObject> proto = Handle<JSObject>::cast(maybe_proto);
Handle<PrototypeInfo> proto_info =
Map::GetOrCreatePrototypeInfo(proto, isolate);
Handle<Object> maybe_registry(proto_info->prototype_users(), isolate);
Handle<WeakArrayList> registry =
maybe_registry->IsSmi()
? handle(ReadOnlyRoots(isolate->heap()).empty_weak_array_list(),
isolate)
: Handle<WeakArrayList>::cast(maybe_registry);
int slot = 0;
Handle<WeakArrayList> new_array =
PrototypeUsers::Add(isolate, registry, current_user, &slot);
current_user_info->set_registry_slot(slot);
if (!maybe_registry.is_identical_to(new_array)) {
proto_info->set_prototype_users(*new_array);
}
if (FLAG_trace_prototype_users) {
PrintF("Registering %p as a user of prototype %p (map=%p).\n",
reinterpret_cast<void*>(current_user->ptr()),
reinterpret_cast<void*>(proto->ptr()),
reinterpret_cast<void*>(proto->map().ptr()));
}
current_user = handle(proto->map(), isolate);
current_user_info = proto_info;
}
}
// Can be called regardless of whether |user| was actually registered with
// |prototype|. Returns true when there was a registration.
// static
bool JSObject::UnregisterPrototypeUser(Handle<Map> user, Isolate* isolate) {
DCHECK(user->is_prototype_map());
// If it doesn't have a PrototypeInfo, it was never registered.
if (!user->prototype_info().IsPrototypeInfo()) return false;
// If it had no prototype before, see if it had users that might expect
// registration.
if (!user->prototype().IsJSObject()) {
Object users =
PrototypeInfo::cast(user->prototype_info()).prototype_users();
return users.IsWeakArrayList();
}
Handle<JSObject> prototype(JSObject::cast(user->prototype()), isolate);
Handle<PrototypeInfo> user_info =
Map::GetOrCreatePrototypeInfo(user, isolate);
int slot = user_info->registry_slot();
if (slot == PrototypeInfo::UNREGISTERED) return false;
DCHECK(prototype->map().is_prototype_map());
Object maybe_proto_info = prototype->map().prototype_info();
// User knows its registry slot, prototype info and user registry must exist.
DCHECK(maybe_proto_info.IsPrototypeInfo());
Handle<PrototypeInfo> proto_info(PrototypeInfo::cast(maybe_proto_info),
isolate);
Handle<WeakArrayList> prototype_users(
WeakArrayList::cast(proto_info->prototype_users()), isolate);
DCHECK_EQ(prototype_users->Get(slot), HeapObjectReference::Weak(*user));
PrototypeUsers::MarkSlotEmpty(*prototype_users, slot);
if (FLAG_trace_prototype_users) {
PrintF("Unregistering %p as a user of prototype %p.\n",
reinterpret_cast<void*>(user->ptr()),
reinterpret_cast<void*>(prototype->ptr()));
}
return true;
}
namespace {
// This function must be kept in sync with
// AccessorAssembler::InvalidateValidityCellIfPrototype() which does pre-checks
// before jumping here.
void InvalidateOnePrototypeValidityCellInternal(Map map) {
DCHECK(map.is_prototype_map());
if (FLAG_trace_prototype_users) {
PrintF("Invalidating prototype map %p 's cell\n",
reinterpret_cast<void*>(map.ptr()));
}
Object maybe_cell = map.prototype_validity_cell();
if (maybe_cell.IsCell()) {
// Just set the value; the cell will be replaced lazily.
Cell cell = Cell::cast(maybe_cell);
cell.set_value(Smi::FromInt(Map::kPrototypeChainInvalid));
}
Object maybe_prototype_info = map.prototype_info();
if (maybe_prototype_info.IsPrototypeInfo()) {
PrototypeInfo prototype_info = PrototypeInfo::cast(maybe_prototype_info);
prototype_info.set_prototype_chain_enum_cache(Object());
}
}
void InvalidatePrototypeChainsInternal(Map map) {
// We handle linear prototype chains by looping, and multiple children
// by recursion, in order to reduce the likelihood of running into stack
// overflows. So, conceptually, the outer loop iterates the depth of the
// prototype tree, and the inner loop iterates the breadth of a node.
Map next_map;
for (; !map.is_null(); map = next_map, next_map = Map()) {
InvalidateOnePrototypeValidityCellInternal(map);
Object maybe_proto_info = map.prototype_info();
if (!maybe_proto_info.IsPrototypeInfo()) return;
PrototypeInfo proto_info = PrototypeInfo::cast(maybe_proto_info);
if (!proto_info.prototype_users().IsWeakArrayList()) {
return;
}
WeakArrayList prototype_users =
WeakArrayList::cast(proto_info.prototype_users());
// For now, only maps register themselves as users.
for (int i = PrototypeUsers::kFirstIndex; i < prototype_users.length();
++i) {
HeapObject heap_object;
if (prototype_users.Get(i)->GetHeapObjectIfWeak(&heap_object) &&
heap_object.IsMap()) {
// Walk the prototype chain (backwards, towards leaf objects) if
// necessary.
if (next_map.is_null()) {
next_map = Map::cast(heap_object);
} else {
InvalidatePrototypeChainsInternal(Map::cast(heap_object));
}
}
}
}
}
} // namespace
// static
Map JSObject::InvalidatePrototypeChains(Map map) {
DisallowHeapAllocation no_gc;
InvalidatePrototypeChainsInternal(map);
return map;
}
// We also invalidate global objects validity cell when a new lexical
// environment variable is added. This is necessary to ensure that
// Load/StoreGlobalIC handlers that load/store from global object's prototype
// get properly invalidated.
// Note, that the normal Load/StoreICs that load/store through the global object
// in the prototype chain are not affected by appearance of a new lexical
// variable and therefore we don't propagate invalidation down.
// static
void JSObject::InvalidatePrototypeValidityCell(JSGlobalObject global) {
DisallowHeapAllocation no_gc;
InvalidateOnePrototypeValidityCellInternal(global.map());
}
Maybe<bool> JSObject::SetPrototype(Handle<JSObject> object,
Handle<Object> value, bool from_javascript,
ShouldThrow should_throw) {
Isolate* isolate = object->GetIsolate();
#ifdef DEBUG
int size = object->Size();
#endif
if (from_javascript) {
if (object->IsAccessCheckNeeded() &&
!isolate->MayAccess(handle(isolate->context(), isolate), object)) {
isolate->ReportFailedAccessCheck(object);
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, Nothing<bool>());
RETURN_FAILURE(isolate, should_throw,
NewTypeError(MessageTemplate::kNoAccess));
}
} else {
DCHECK(!object->IsAccessCheckNeeded());
}
// Silently ignore the change if value is not a JSObject or null.
// SpiderMonkey behaves this way.
if (!value->IsJSReceiver() && !value->IsNull(isolate)) return Just(true);
bool all_extensible = object->map().is_extensible();
Handle<JSObject> real_receiver = object;
if (from_javascript) {
// Find the first object in the chain whose prototype object is not
// hidden.
PrototypeIterator iter(isolate, real_receiver, kStartAtPrototype,
PrototypeIterator::END_AT_NON_HIDDEN);
while (!iter.IsAtEnd()) {
// Casting to JSObject is fine because hidden prototypes are never
// JSProxies.
real_receiver = PrototypeIterator::GetCurrent<JSObject>(iter);
iter.Advance();
all_extensible = all_extensible && real_receiver->map().is_extensible();
}
}
Handle<Map> map(real_receiver->map(), isolate);
// Nothing to do if prototype is already set.
if (map->prototype() == *value) return Just(true);
bool immutable_proto = map->is_immutable_proto();
if (immutable_proto) {
RETURN_FAILURE(
isolate, should_throw,
NewTypeError(MessageTemplate::kImmutablePrototypeSet, object));
}
// From 6.1.7.3 Invariants of the Essential Internal Methods
//
// [[SetPrototypeOf]] ( V )
// * ...
// * If target is non-extensible, [[SetPrototypeOf]] must return false,
// unless V is the SameValue as the target's observed [[GetPrototypeOf]]
// value.
if (!all_extensible) {
RETURN_FAILURE(isolate, should_throw,
NewTypeError(MessageTemplate::kNonExtensibleProto, object));
}
// Before we can set the prototype we need to be sure prototype cycles are
// prevented. It is sufficient to validate that the receiver is not in the
// new prototype chain.
if (value->IsJSReceiver()) {
for (PrototypeIterator iter(isolate, JSReceiver::cast(*value),
kStartAtReceiver);
!iter.IsAtEnd(); iter.Advance()) {
if (iter.GetCurrent<JSReceiver>() == *object) {
// Cycle detected.
RETURN_FAILURE(isolate, should_throw,
NewTypeError(MessageTemplate::kCyclicProto));
}
}
}
// Set the new prototype of the object.
isolate->UpdateNoElementsProtectorOnSetPrototype(real_receiver);
Handle<Map> new_map =
Map::TransitionToPrototype(isolate, map, Handle<HeapObject>::cast(value));
DCHECK(new_map->prototype() == *value);
JSObject::MigrateToMap(isolate, real_receiver, new_map);
DCHECK(size == object->Size());
return Just(true);
}
// static
void JSObject::SetImmutableProto(Handle<JSObject> object) {
Handle<Map> map(object->map(), object->GetIsolate());
// Nothing to do if prototype is already set.
if (map->is_immutable_proto()) return;
Handle<Map> new_map =
Map::TransitionToImmutableProto(object->GetIsolate(), map);
object->synchronized_set_map(*new_map);
}
void JSObject::EnsureCanContainElements(Handle<JSObject> object,
JavaScriptArguments* args,
uint32_t arg_count,
EnsureElementsMode mode) {
return EnsureCanContainElements(object, args->first_slot(), arg_count, mode);
}
void JSObject::ValidateElements(JSObject object) {
#ifdef ENABLE_SLOW_DCHECKS
if (FLAG_enable_slow_asserts) {
object.GetElementsAccessor()->Validate(object);
}
#endif
}
bool JSObject::WouldConvertToSlowElements(uint32_t index) {
if (!HasFastElements()) return false;
uint32_t capacity = static_cast<uint32_t>(elements().length());
uint32_t new_capacity;
return ShouldConvertToSlowElements(*this, capacity, index, &new_capacity);
}
static bool ShouldConvertToFastElements(JSObject object,
NumberDictionary dictionary,
uint32_t index,
uint32_t* new_capacity) {
// If properties with non-standard attributes or accessors were added, we
// cannot go back to fast elements.
if (dictionary.requires_slow_elements()) return false;
// Adding a property with this index will require slow elements.
if (index >= static_cast<uint32_t>(Smi::kMaxValue)) return false;
if (object.IsJSArray()) {
Object length = JSArray::cast(object).length();
if (!length.IsSmi()) return false;
*new_capacity = static_cast<uint32_t>(Smi::ToInt(length));
} else if (object.IsJSArgumentsObject()) {
return false;
} else {
*new_capacity = dictionary.max_number_key() + 1;
}
*new_capacity = std::max(index + 1, *new_capacity);
uint32_t dictionary_size = static_cast<uint32_t>(dictionary.Capacity()) *
NumberDictionary::kEntrySize;
// Turn fast if the dictionary only saves 50% space.
return 2 * dictionary_size >= *new_capacity;
}
static ElementsKind BestFittingFastElementsKind(JSObject object) {
if (!object.map().CanHaveFastTransitionableElementsKind()) {
return HOLEY_ELEMENTS;
}
if (object.HasSloppyArgumentsElements()) {
return FAST_SLOPPY_ARGUMENTS_ELEMENTS;
}
if (object.HasStringWrapperElements()) {
return FAST_STRING_WRAPPER_ELEMENTS;
}
DCHECK(object.HasDictionaryElements());
NumberDictionary dictionary = object.element_dictionary();
ElementsKind kind = HOLEY_SMI_ELEMENTS;
for (InternalIndex i : dictionary.IterateEntries()) {
Object key = dictionary.KeyAt(i);
if (key.IsNumber()) {
Object value = dictionary.ValueAt(i);
if (!value.IsNumber()) return HOLEY_ELEMENTS;
if (!value.IsSmi()) {
if (!FLAG_unbox_double_arrays) return HOLEY_ELEMENTS;
kind = HOLEY_DOUBLE_ELEMENTS;
}
}
}
return kind;
}
// static
void JSObject::AddDataElement(Handle<JSObject> object, uint32_t index,
Handle<Object> value,
PropertyAttributes attributes) {
Isolate* isolate = object->GetIsolate();
DCHECK(object->map(isolate).is_extensible());
uint32_t old_length = 0;
uint32_t new_capacity = 0;
if (object->IsJSArray(isolate)) {
CHECK(JSArray::cast(*object).length().ToArrayLength(&old_length));
}
ElementsKind kind = object->GetElementsKind(isolate);
FixedArrayBase elements = object->elements(isolate);
ElementsKind dictionary_kind = DICTIONARY_ELEMENTS;
if (IsSloppyArgumentsElementsKind(kind)) {
elements = SloppyArgumentsElements::cast(elements).arguments(isolate);
dictionary_kind = SLOW_SLOPPY_ARGUMENTS_ELEMENTS;
} else if (IsStringWrapperElementsKind(kind)) {
dictionary_kind = SLOW_STRING_WRAPPER_ELEMENTS;
}
if (attributes != NONE) {
kind = dictionary_kind;
} else if (elements.IsNumberDictionary(isolate)) {
kind = ShouldConvertToFastElements(
*object, NumberDictionary::cast(elements), index, &new_capacity)
? BestFittingFastElementsKind(*object)
: dictionary_kind;
} else if (ShouldConvertToSlowElements(
*object, static_cast<uint32_t>(elements.length()), index,
&new_capacity)) {
kind = dictionary_kind;
}
ElementsKind to = value->OptimalElementsKind(isolate);
if (IsHoleyElementsKind(kind) || !object->IsJSArray(isolate) ||
index > old_length) {
to = GetHoleyElementsKind(to);
kind = GetHoleyElementsKind(kind);
}
to = GetMoreGeneralElementsKind(kind, to);
ElementsAccessor* accessor = ElementsAccessor::ForKind(to);
accessor->Add(object, index, value, attributes, new_capacity);
if (object->IsJSArray(isolate) && index >= old_length) {
Handle<Object> new_length =
isolate->factory()->NewNumberFromUint(index + 1);
JSArray::cast(*object).set_length(*new_length);
}
}
template <AllocationSiteUpdateMode update_or_check>
bool JSObject::UpdateAllocationSite(Handle<JSObject> object,
ElementsKind to_kind) {
if (!object->IsJSArray()) return false;
if (!Heap::InYoungGeneration(*object)) return false;
if (Heap::IsLargeObject(*object)) return false;
Handle<AllocationSite> site;
{
DisallowHeapAllocation no_allocation;
Heap* heap = object->GetHeap();
AllocationMemento memento =
heap->FindAllocationMemento<Heap::kForRuntime>(object->map(), *object);
if (memento.is_null()) return false;
// Walk through to the Allocation Site
site = handle(memento.GetAllocationSite(), heap->isolate());
}
return AllocationSite::DigestTransitionFeedback<update_or_check>(site,
to_kind);
}
template bool
JSObject::UpdateAllocationSite<AllocationSiteUpdateMode::kCheckOnly>(
Handle<JSObject> object, ElementsKind to_kind);
template bool JSObject::UpdateAllocationSite<AllocationSiteUpdateMode::kUpdate>(
Handle<JSObject> object, ElementsKind to_kind);
void JSObject::TransitionElementsKind(Handle<JSObject> object,
ElementsKind to_kind) {
ElementsKind from_kind = object->GetElementsKind();
if (IsHoleyElementsKind(from_kind)) {
to_kind = GetHoleyElementsKind(to_kind);
}
if (from_kind == to_kind) return;
// This method should never be called for any other case.
DCHECK(IsFastElementsKind(from_kind) ||
IsNonextensibleElementsKind(from_kind));
DCHECK(IsFastElementsKind(to_kind) || IsNonextensibleElementsKind(to_kind));
DCHECK_NE(TERMINAL_FAST_ELEMENTS_KIND, from_kind);
UpdateAllocationSite(object, to_kind);
Isolate* isolate = object->GetIsolate();
if (object->elements() == ReadOnlyRoots(isolate).empty_fixed_array() ||
IsDoubleElementsKind(from_kind) == IsDoubleElementsKind(to_kind)) {
// No change is needed to the elements() buffer, the transition
// only requires a map change.
Handle<Map> new_map = GetElementsTransitionMap(object, to_kind);
JSObject::MigrateToMap(isolate, object, new_map);
if (FLAG_trace_elements_transitions) {
Handle<FixedArrayBase> elms(object->elements(), isolate);
PrintElementsTransition(stdout, object, from_kind, elms, to_kind, elms);
}
} else {
DCHECK((IsSmiElementsKind(from_kind) && IsDoubleElementsKind(to_kind)) ||
(IsDoubleElementsKind(from_kind) && IsObjectElementsKind(to_kind)));
uint32_t c = static_cast<uint32_t>(object->elements().length());
ElementsAccessor::ForKind(to_kind)->GrowCapacityAndConvert(object, c);
}
}
template <typename BackingStore>
static int HoleyElementsUsage(JSObject object, BackingStore store) {
Isolate* isolate = object.GetIsolate();
int limit = object.IsJSArray() ? Smi::ToInt(JSArray::cast(object).length())
: store.length();
int used = 0;
for (int i = 0; i < limit; ++i) {
if (!store.is_the_hole(isolate, i)) ++used;
}
return used;
}
int JSObject::GetFastElementsUsage() {
FixedArrayBase store = elements();
switch (GetElementsKind()) {
case PACKED_SMI_ELEMENTS:
case PACKED_DOUBLE_ELEMENTS:
case PACKED_ELEMENTS:
case PACKED_FROZEN_ELEMENTS:
case PACKED_SEALED_ELEMENTS:
case PACKED_NONEXTENSIBLE_ELEMENTS:
return IsJSArray() ? Smi::ToInt(JSArray::cast(*this).length())
: store.length();
case FAST_SLOPPY_ARGUMENTS_ELEMENTS:
store = SloppyArgumentsElements::cast(store).arguments();
V8_FALLTHROUGH;
case HOLEY_SMI_ELEMENTS:
case HOLEY_ELEMENTS:
case HOLEY_FROZEN_ELEMENTS:
case HOLEY_SEALED_ELEMENTS:
case HOLEY_NONEXTENSIBLE_ELEMENTS:
case FAST_STRING_WRAPPER_ELEMENTS:
return HoleyElementsUsage(*this, FixedArray::cast(store));
case HOLEY_DOUBLE_ELEMENTS:
if (elements().length() == 0) return 0;
return HoleyElementsUsage(*this, FixedDoubleArray::cast(store));
case SLOW_SLOPPY_ARGUMENTS_ELEMENTS:
case SLOW_STRING_WRAPPER_ELEMENTS:
case DICTIONARY_ELEMENTS:
case NO_ELEMENTS:
#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) case TYPE##_ELEMENTS:
TYPED_ARRAYS(TYPED_ARRAY_CASE)
#undef TYPED_ARRAY_CASE
UNREACHABLE();
}
return 0;
}
MaybeHandle<Object> JSObject::GetPropertyWithInterceptor(LookupIterator* it,
bool* done) {
DCHECK_EQ(LookupIterator::INTERCEPTOR, it->state());
return GetPropertyWithInterceptorInternal(it, it->GetInterceptor(), done);
}
Maybe<bool> JSObject::HasRealNamedProperty(Handle<JSObject> object,
Handle<Name> name) {
Isolate* isolate = object->GetIsolate();
LookupIterator::Key key(isolate, name);
LookupIterator it(isolate, object, key, LookupIterator::OWN_SKIP_INTERCEPTOR);
return HasProperty(&it);
}
Maybe<bool> JSObject::HasRealElementProperty(Handle<JSObject> object,
uint32_t index) {
Isolate* isolate = object->GetIsolate();
LookupIterator it(isolate, object, index, object,
LookupIterator::OWN_SKIP_INTERCEPTOR);
return HasProperty(&it);
}
Maybe<bool> JSObject::HasRealNamedCallbackProperty(Handle<JSObject> object,
Handle<Name> name) {
Isolate* isolate = object->GetIsolate();
LookupIterator::Key key(isolate, name);
LookupIterator it(isolate, object, key, LookupIterator::OWN_SKIP_INTERCEPTOR);
Maybe<PropertyAttributes> maybe_result = GetPropertyAttributes(&it);
return maybe_result.IsJust() ? Just(it.state() == LookupIterator::ACCESSOR)
: Nothing<bool>();
}
bool JSObject::IsApiWrapper() {
// These object types can carry information relevant for embedders. The
// *_API_* types are generated through templates which can have embedder
// fields. The other types have their embedder fields added at compile time.
auto instance_type = map().instance_type();
return instance_type == JS_API_OBJECT_TYPE ||
instance_type == JS_ARRAY_BUFFER_TYPE ||
instance_type == JS_DATA_VIEW_TYPE ||
instance_type == JS_GLOBAL_OBJECT_TYPE ||
instance_type == JS_GLOBAL_PROXY_TYPE ||
instance_type == JS_SPECIAL_API_OBJECT_TYPE ||
instance_type == JS_TYPED_ARRAY_TYPE;
}
bool JSObject::IsDroppableApiWrapper() {
auto instance_type = map().instance_type();
return instance_type == JS_API_OBJECT_TYPE ||
instance_type == JS_SPECIAL_API_OBJECT_TYPE;
}
bool JSGlobalProxy::IsDetached() const {
return native_context().IsNull(GetIsolate());
}
void JSGlobalObject::InvalidatePropertyCell(Handle<JSGlobalObject> global,
Handle<Name> name) {
// Regardless of whether the property is there or not invalidate
// Load/StoreGlobalICs that load/store through global object's prototype.
JSObject::InvalidatePrototypeValidityCell(*global);
DCHECK(!global->HasFastProperties());
auto dictionary = handle(global->global_dictionary(), global->GetIsolate());
InternalIndex entry = dictionary->FindEntry(global->GetIsolate(), name);
if (entry.is_not_found()) return;
PropertyCell::InvalidateAndReplaceEntry(global->GetIsolate(), dictionary,
entry);
}
// static
MaybeHandle<JSDate> JSDate::New(Handle<JSFunction> constructor,
Handle<JSReceiver> new_target, double tv) {
Isolate* const isolate = constructor->GetIsolate();
Handle<JSObject> result;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result,
JSObject::New(constructor, new_target, Handle<AllocationSite>::null()),
JSDate);
if (-DateCache::kMaxTimeInMs <= tv && tv <= DateCache::kMaxTimeInMs) {
tv = DoubleToInteger(tv) + 0.0;
} else {
tv = std::numeric_limits<double>::quiet_NaN();
}
Handle<Object> value = isolate->factory()->NewNumber(tv);
Handle<JSDate>::cast(result)->SetValue(*value, std::isnan(tv));
return Handle<JSDate>::cast(result);
}
// static
double JSDate::CurrentTimeValue(Isolate* isolate) {
if (FLAG_log_internal_timer_events) LOG(isolate, CurrentTimeEvent());
// According to ECMA-262, section 15.9.1, page 117, the precision of
// the number in a Date object representing a particular instant in
// time is milliseconds. Therefore, we floor the result of getting
// the OS time.
return std::floor(V8::GetCurrentPlatform()->CurrentClockTimeMillis());
}
// static
Address JSDate::GetField(Isolate* isolate, Address raw_object,
Address smi_index) {
// Called through CallCFunction.
DisallowHeapAllocation no_gc;
DisallowHandleAllocation no_handles;
DisallowJavascriptExecution no_js(isolate);
Object object(raw_object);
Smi index(smi_index);
return JSDate::cast(object)
.DoGetField(isolate, static_cast<FieldIndex>(index.value()))
.ptr();
}
Object JSDate::DoGetField(Isolate* isolate, FieldIndex index) {
DCHECK_NE(index, kDateValue);
DateCache* date_cache = isolate->date_cache();
if (index < kFirstUncachedField) {
Object stamp = cache_stamp();
if (stamp != date_cache->stamp() && stamp.IsSmi()) {
// Since the stamp is not NaN, the value is also not NaN.
int64_t local_time_ms =
date_cache->ToLocal(static_cast<int64_t>(value().Number()));
SetCachedFields(local_time_ms, date_cache);
}
switch (index) {
case kYear:
return year();
case kMonth:
return month();
case kDay:
return day();
case kWeekday:
return weekday();
case kHour:
return hour();
case kMinute:
return min();
case kSecond:
return sec();
default:
UNREACHABLE();
}
}
if (index >= kFirstUTCField) {
return GetUTCField(index, value().Number(), date_cache);
}
double time = value().Number();
if (std::isnan(time)) return GetReadOnlyRoots().nan_value();
int64_t local_time_ms = date_cache->ToLocal(static_cast<int64_t>(time));
int days = DateCache::DaysFromTime(local_time_ms);
if (index == kDays) return Smi::FromInt(days);
int time_in_day_ms = DateCache::TimeInDay(local_time_ms, days);
if (index == kMillisecond) return Smi::FromInt(time_in_day_ms % 1000);
DCHECK_EQ(index, kTimeInDay);
return Smi::FromInt(time_in_day_ms);
}
Object JSDate::GetUTCField(FieldIndex index, double value,
DateCache* date_cache) {
DCHECK_GE(index, kFirstUTCField);
if (std::isnan(value)) return GetReadOnlyRoots().nan_value();
int64_t time_ms = static_cast<int64_t>(value);
if (index == kTimezoneOffset) {
return Smi::FromInt(date_cache->TimezoneOffset(time_ms));
}
int days = DateCache::DaysFromTime(time_ms);
if (index == kWeekdayUTC) return Smi::FromInt(date_cache->Weekday(days));
if (index <= kDayUTC) {
int year, month, day;
date_cache->YearMonthDayFromDays(days, &year, &month, &day);
if (index == kYearUTC) return Smi::FromInt(year);
if (index == kMonthUTC) return Smi::FromInt(month);
DCHECK_EQ(index, kDayUTC);
return Smi::FromInt(day);
}
int time_in_day_ms = DateCache::TimeInDay(time_ms, days);
switch (index) {
case kHourUTC:
return Smi::FromInt(time_in_day_ms / (60 * 60 * 1000));
case kMinuteUTC:
return Smi::FromInt((time_in_day_ms / (60 * 1000)) % 60);
case kSecondUTC:
return Smi::FromInt((time_in_day_ms / 1000) % 60);
case kMillisecondUTC:
return Smi::FromInt(time_in_day_ms % 1000);
case kDaysUTC:
return Smi::FromInt(days);
case kTimeInDayUTC:
return Smi::FromInt(time_in_day_ms);
default:
UNREACHABLE();
}
UNREACHABLE();
}
// static
Handle<Object> JSDate::SetValue(Handle<JSDate> date, double v) {
Isolate* const isolate = date->GetIsolate();
Handle<Object> value = isolate->factory()->NewNumber(v);
bool value_is_nan = std::isnan(v);
date->SetValue(*value, value_is_nan);
return value;
}
void JSDate::SetValue(Object value, bool is_value_nan) {
set_value(value);
if (is_value_nan) {
HeapNumber nan = GetReadOnlyRoots().nan_value();
set_cache_stamp(nan, SKIP_WRITE_BARRIER);
set_year(nan, SKIP_WRITE_BARRIER);
set_month(nan, SKIP_WRITE_BARRIER);
set_day(nan, SKIP_WRITE_BARRIER);
set_hour(nan, SKIP_WRITE_BARRIER);
set_min(nan, SKIP_WRITE_BARRIER);
set_sec(nan, SKIP_WRITE_BARRIER);
set_weekday(nan, SKIP_WRITE_BARRIER);
} else {
set_cache_stamp(Smi::FromInt(DateCache::kInvalidStamp), SKIP_WRITE_BARRIER);
}
}
void JSDate::SetCachedFields(int64_t local_time_ms, DateCache* date_cache) {
int days = DateCache::DaysFromTime(local_time_ms);
int time_in_day_ms = DateCache::TimeInDay(local_time_ms, days);
int year, month, day;
date_cache->YearMonthDayFromDays(days, &year, &month, &day);
int weekday = date_cache->Weekday(days);
int hour = time_in_day_ms / (60 * 60 * 1000);
int min = (time_in_day_ms / (60 * 1000)) % 60;
int sec = (time_in_day_ms / 1000) % 60;
set_cache_stamp(date_cache->stamp());
set_year(Smi::FromInt(year), SKIP_WRITE_BARRIER);
set_month(Smi::FromInt(month), SKIP_WRITE_BARRIER);
set_day(Smi::FromInt(day), SKIP_WRITE_BARRIER);
set_weekday(Smi::FromInt(weekday), SKIP_WRITE_BARRIER);
set_hour(Smi::FromInt(hour), SKIP_WRITE_BARRIER);
set_min(Smi::FromInt(min), SKIP_WRITE_BARRIER);
set_sec(Smi::FromInt(sec), SKIP_WRITE_BARRIER);
}
// static
void JSMessageObject::EnsureSourcePositionsAvailable(
Isolate* isolate, Handle<JSMessageObject> message) {
if (!message->DidEnsureSourcePositionsAvailable()) {
DCHECK_EQ(message->start_position(), -1);
DCHECK_GE(message->bytecode_offset().value(), kFunctionEntryBytecodeOffset);
Handle<SharedFunctionInfo> shared_info(
SharedFunctionInfo::cast(message->shared_info()), isolate);
SharedFunctionInfo::EnsureSourcePositionsAvailable(isolate, shared_info);
DCHECK(shared_info->HasBytecodeArray());
int position = shared_info->abstract_code().SourcePosition(
message->bytecode_offset().value());
DCHECK_GE(position, 0);
message->set_start_position(position);
message->set_end_position(position + 1);
message->set_shared_info(ReadOnlyRoots(isolate).undefined_value());
}
}
int JSMessageObject::GetLineNumber() const {
DCHECK(DidEnsureSourcePositionsAvailable());
if (start_position() == -1) return Message::kNoLineNumberInfo;
Handle<Script> the_script(script(), GetIsolate());
Script::PositionInfo info;
const Script::OffsetFlag offset_flag = Script::WITH_OFFSET;
if (!Script::GetPositionInfo(the_script, start_position(), &info,
offset_flag)) {
return Message::kNoLineNumberInfo;
}
return info.line + 1;
}
int JSMessageObject::GetColumnNumber() const {
DCHECK(DidEnsureSourcePositionsAvailable());
if (start_position() == -1) return -1;
Handle<Script> the_script(script(), GetIsolate());
Script::PositionInfo info;
const Script::OffsetFlag offset_flag = Script::WITH_OFFSET;
if (!Script::GetPositionInfo(the_script, start_position(), &info,
offset_flag)) {
return -1;
}
return info.column; // Note: No '+1' in contrast to GetLineNumber.
}
Handle<String> JSMessageObject::GetSourceLine() const {
Isolate* isolate = GetIsolate();
Handle<Script> the_script(script(), isolate);
if (the_script->type() == Script::TYPE_WASM) {
return isolate->factory()->empty_string();
}
Script::PositionInfo info;
const Script::OffsetFlag offset_flag = Script::WITH_OFFSET;
DCHECK(DidEnsureSourcePositionsAvailable());
if (!Script::GetPositionInfo(the_script, start_position(), &info,
offset_flag)) {
return isolate->factory()->empty_string();
}
Handle<String> src = handle(String::cast(the_script->source()), isolate);
return isolate->factory()->NewSubString(src, info.line_start, info.line_end);
}
} // namespace internal
} // namespace v8