/
githubmirror
/
node
Обзор
Документация
Войти
/
githubmirror
/
node
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
deps/v8/src/objects/js-objects.cc
6 094 строки
236 KB
Chengzhong Wu
deps: V8: backport a05321ebd98e
07 июл 2026, 20:59
07 июл 2026, 20:59
39d9e8c
Код
Авторство
О чём код?
// 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 <limits> #include <optional> #include "src/api/api-arguments-inl.h" #include "src/api/api-natives.h" #include "src/common/assert-scope.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-utils.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/heap-layout-inl.h" #include "src/heap/mutable-page.h" #include "src/heap/pretenuring-handler-inl.h" #include "src/heap/read-only-heap.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/heap-object.h" #include "src/objects/js-array-buffer-inl.h" #include "src/objects/js-array-inl.h" #include "src/objects/js-atomics-synchronization.h" #include "src/objects/js-collection.h" #include "src/objects/js-disposable-stack.h" #include "src/objects/js-generator-inl.h" #include "src/objects/js-iterator-helpers-inl.h" #include "src/objects/js-promise.h" #include "src/objects/js-raw-json-inl.h" #include "src/objects/js-regexp-inl.h" #include "src/objects/js-regexp-string-iterator.h" #include "src/objects/js-shadow-realm.h" #include "src/objects/js-shared-array-inl.h" #include "src/objects/js-struct-inl.h" #ifdef V8_TEMPORAL_SUPPORT #include "src/objects/js-temporal-objects-inl.h" #endif // V8_TEMPORAL_SUPPORT #include "src/objects/js-weak-refs.h" #include "src/objects/lookup.h" #include "src/objects/map-inl.h" #include "src/objects/map-updater.h" #include "src/objects/module.h" #include "src/objects/objects-body-descriptors-inl.h" #include "src/objects/objects-inl.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/swiss-name-dictionary-inl.h" #include "src/objects/tagged.h" #include "src/objects/transitions.h" #include "src/strings/string-builder-inl.h" #include "src/strings/string-stream.h" #include "src/utils/ostreams.h" #if V8_ENABLE_WEBASSEMBLY #include "src/debug/debug-wasm-objects.h" #include "src/wasm/wasm-objects.h" #endif // V8_ENABLE_WEBASSEMBLY #ifdef V8_INTL_SUPPORT #include "src/objects/js-break-iterator.h" #include "src/objects/js-collator.h" #include "src/objects/js-date-time-format.h" #include "src/objects/js-display-names.h" #include "src/objects/js-duration-format.h" #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" #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 namespace v8::internal { // static Maybe<bool> JSReceiver::HasProperty(LookupIterator* it) { for (;; it->Next()) { switch (it->state()) { case LookupIterator::TRANSITION: case LookupIterator::STRING_LOOKUP_START_OBJECT: UNREACHABLE(); case LookupIterator::JSPROXY: return JSProxy::HasProperty(it->isolate(), it->GetHolder<JSProxy>(), it->GetName()); case LookupIterator::WASM_OBJECT: continue; // Continue to the prototype, if present. case LookupIterator::INTERCEPTOR: { Maybe<PropertyAttributes> result = JSObject::GetPropertyAttributesWithInterceptor(it); if (result.IsNothing()) return Nothing<bool>(); if (result.FromJust() != ABSENT) return Just(true); continue; } case LookupIterator::ACCESS_CHECK: { if (it->HasAccess()) continue; Maybe<PropertyAttributes> result = JSObject::GetPropertyAttributesWithFailedAccessCheck(it); if (result.IsNothing()) return Nothing<bool>(); return Just(result.FromJust() != ABSENT); } case LookupIterator::TYPED_ARRAY_INDEX_NOT_FOUND: // TypedArray out-of-bounds access. return Just(false); case LookupIterator::MODULE_NAMESPACE: { if (JSDeferredModuleNamespace::TriggersEvaluation(it)) { DirectHandle<JSDeferredModuleNamespace> holder = it->GetHolder<JSDeferredModuleNamespace>(); JSDeferredModuleNamespace::EvaluateModuleSync(it->isolate(), holder); RETURN_EXCEPTION_IF_EXCEPTION(it->isolate()); } continue; } case LookupIterator::ACCESSOR: case LookupIterator::DATA: return Just(true); case LookupIterator::NOT_FOUND: return Just(false); } UNREACHABLE(); } } // static Maybe<bool> JSReceiver::HasOwnProperty(Isolate* isolate, DirectHandle<JSReceiver> object, DirectHandle<Name> name) { if (IsJSModuleNamespace(*object)) { PropertyDescriptor desc; return JSReceiver::GetOwnPropertyDescriptor(isolate, object, name, &desc); } if (IsJSObject(*object)) { // Shortcut. PropertyKey key(isolate, name); LookupIterator it(isolate, object, key, LookupIterator::OWN); return HasProperty(&it); } Maybe<PropertyAttributes> attributes = JSReceiver::GetOwnPropertyAttributes(isolate, object, name); MAYBE_RETURN(attributes, Nothing<bool>()); return Just(attributes.FromJust() != ABSENT); } Handle<Object> JSReceiver::GetDataProperty(LookupIterator* it, AllocationPolicy allocation_policy) { for (;; it->Next()) { switch (it->state()) { case LookupIterator::INTERCEPTOR: case LookupIterator::TRANSITION: case LookupIterator::STRING_LOOKUP_START_OBJECT: 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; [[fallthrough]]; case LookupIterator::JSPROXY: it->NotFound(); return it->isolate()->factory()->undefined_value(); case LookupIterator::WASM_OBJECT: continue; // Continue to the prototype, if present. case LookupIterator::ACCESSOR: { auto accessors = it->GetAccessors(); // Special handling for AccessorInfo, which behaves like a data // property. if (IsAccessorInfo(*accessors)) { auto info = Cast<AccessorInfo>(*accessors); if (info->getter_side_effect_type() == SideEffectType::kHasNoSideEffect) { v8::TryCatch try_catch( reinterpret_cast<v8::Isolate*>(it->isolate())); try_catch.SetVerbose(false); try_catch.SetCaptureMessage(false); Handle<Object> result; if (Object::GetPropertyWithAccessor(it).ToHandle(&result)) { return result; } } } it->NotFound(); return it->isolate()->factory()->undefined_value(); } case LookupIterator::TYPED_ARRAY_INDEX_NOT_FOUND: return it->isolate()->factory()->undefined_value(); case LookupIterator::DATA: return it->GetDataValue(allocation_policy); case LookupIterator::NOT_FOUND: return it->isolate()->factory()->undefined_value(); case LookupIterator::MODULE_NAMESPACE: { DirectHandle<JSModuleNamespace> ns = it->GetHolder<JSModuleNamespace>(); if (IsJSDeferredModuleNamespace(*ns) && JSDeferredModuleNamespace::TriggersEvaluation(it)) { it->NotFound(); return it->isolate()->factory()->undefined_value(); } continue; } } UNREACHABLE(); } } // static Maybe<bool> JSReceiver::HasInPrototypeChain(Isolate* isolate, DirectHandle<JSReceiver> object, DirectHandle<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); } } } // static Maybe<bool> JSReceiver::CheckPrivateNameStore(LookupIterator* it, bool is_define) { DCHECK(it->GetName()->IsAnyPrivateName()); Isolate* isolate = it->isolate(); DirectHandle<String> name_string( Cast<String>(Cast<Symbol>(it->GetName())->description()), isolate); for (;; it->Next()) { switch (it->state()) { case LookupIterator::MODULE_NAMESPACE: case LookupIterator::TRANSITION: case LookupIterator::INTERCEPTOR: case LookupIterator::JSPROXY: case LookupIterator::TYPED_ARRAY_INDEX_NOT_FOUND: case LookupIterator::ACCESSOR: case LookupIterator::STRING_LOOKUP_START_OBJECT: UNREACHABLE(); case LookupIterator::ACCESS_CHECK: if (!it->HasAccess()) { RETURN_ON_EXCEPTION_VALUE(isolate, isolate->ReportFailedAccessCheck( Cast<JSObject>(it->GetReceiver())), Nothing<bool>()); UNREACHABLE(); } continue; case LookupIterator::DATA: if (is_define) { MessageTemplate message = it->GetName()->IsPrivateBrand() ? MessageTemplate::kInvalidPrivateBrandReinitialization : MessageTemplate::kInvalidPrivateFieldReinitialization; RETURN_FAILURE(isolate, GetShouldThrow(isolate, Nothing<ShouldThrow>()), NewTypeError(message, name_string, it->GetReceiver())); } return Just(true); case LookupIterator::WASM_OBJECT: RETURN_FAILURE(isolate, kThrowOnError, NewTypeError(MessageTemplate::kWasmObjectsAreOpaque)); case LookupIterator::NOT_FOUND: if (!is_define) { RETURN_FAILURE( isolate, GetShouldThrow(isolate, Nothing<ShouldThrow>()), NewTypeError(MessageTemplate::kInvalidPrivateMemberWrite, name_string, it->GetReceiver())); } else if (it->ExtendingNonExtensible( it->GetStoreTarget<JSReceiver>())) { RETURN_FAILURE( isolate, kThrowOnError, NewTypeError(MessageTemplate::kDefineDisallowed, name_string)); } return Just(true); } UNREACHABLE(); } } namespace { bool HasExcludedProperty(base::Vector<DirectHandle<Object>> excluded_properties, DirectHandle<Object> search_element) { // TODO(gsathya): Change this to be a hashtable. for (DirectHandle<Object> object : excluded_properties) { if (Object::SameValue(*search_element, *object)) { return true; } } return false; } // If direct handles are enabled, it is the responsibility of the caller to // ensure that the memory pointed to by `excluded_properties` is scanned // during CSS, e.g., it comes from a `DirectHandleVector<Object>`. V8_WARN_UNUSED_RESULT Maybe<bool> FastAssign( Isolate* isolate, DirectHandle<JSReceiver> target, DirectHandle<Object> source, PropertiesEnumerationMode mode, base::Vector<DirectHandle<Object>> excluded_properties, bool use_set) { // Non-empty strings are the only non-JSReceivers that need to be handled // explicitly by Object.assign. if (!IsJSReceiver(*source)) { return Just(!IsString(*source) || Cast<String>(*source)->length() == 0); } // 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, Cast<JSObject>(target)); } DirectHandle<Map> map(Cast<JSReceiver>(*source)->map(), isolate); if (!IsJSObjectMap(*map)) return Just(false); if (!map->OnlyHasSimpleProperties()) return Just(false); DirectHandle<JSObject> from = Cast<JSObject>(source); if (from->elements() != ReadOnlyRoots(isolate).empty_fixed_array()) { return Just(false); } // We should never try to copy properties from an object itself. CHECK_IMPLIES(!use_set, !target.is_identical_to(from)); bool stable = true; // Process symbols last and only do that if we found symbols. bool has_symbol = false; bool process_symbol_only = false; while (true) { for (InternalIndex i : map->IterateOwnDescriptors()) { HandleScope inner_scope(isolate); // The descriptor array is not cached on purpose since it has to stay in // sync with map->instance_descriptors to avoid it from being pruned. DirectHandle<Name> next_key(map->instance_descriptors(isolate)->GetKey(i), isolate); if (mode == PropertiesEnumerationMode::kEnumerationOrder) { if (IsSymbol(*next_key)) { has_symbol = true; if (!process_symbol_only) continue; } else { if (process_symbol_only) continue; } } DirectHandle<Object> prop_value; // Directly decode from the descriptor array if |from| did not change // shape. if (stable) { DCHECK_EQ(from->map(), *map); PropertyDetails details = map->instance_descriptors(isolate)->GetDetails(i); if (!details.IsEnumerable()) continue; if (details.kind() == PropertyKind::kData) { CHECK_EQ(details.location(), PropertyLocation::kField); Representation representation = details.representation(); FieldIndex index = FieldIndex::ForPropertyIndex( *map, details.field_index(), representation); prop_value = JSObject::FastPropertyAt(isolate, from, representation, index); } else { LookupIterator it(isolate, from, next_key, LookupIterator::OWN_SKIP_INTERCEPTOR); ASSIGN_RETURN_ON_EXCEPTION(isolate, prop_value, Object::GetProperty(&it)); stable = from->map() == *map; } } 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(isolate, prop_value, Object::GetProperty(&it)); } 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. PropertyKey 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; } } else { // No element indexes should get here or the exclusion check may // yield false negatives for type mismatch. if (!excluded_properties.empty() && 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. CHECK(JSReceiver::CreateDataProperty(isolate, target, next_key, prop_value, Just(kThrowOnError)) .FromJust()); } } if (mode == PropertiesEnumerationMode::kEnumerationOrder) { if (process_symbol_only || !has_symbol) { return Just(true); } if (has_symbol) { process_symbol_only = true; } } else { DCHECK_EQ(mode, PropertiesEnumerationMode::kPropertyAdditionOrder); return Just(true); } } UNREACHABLE(); } } // namespace // static Maybe<bool> JSReceiver::SetOrCopyDataProperties( Isolate* isolate, DirectHandle<JSReceiver> target, DirectHandle<Object> source, PropertiesEnumerationMode mode, base::Vector<DirectHandle<Object>> excluded_properties, bool use_set) { Maybe<bool> fast_assign = FastAssign(isolate, target, source, mode, excluded_properties, use_set); if (fast_assign.IsNothing()) return Nothing<bool>(); if (fast_assign.FromJust()) return Just(true); DirectHandle<JSReceiver> from = Object::ToObject(isolate, source).ToHandleChecked(); // 3b. Let keys be ? from.[[OwnPropertyKeys]](). DirectHandle<FixedArray> keys; ASSIGN_RETURN_ON_EXCEPTION( isolate, keys, KeyAccumulator::GetKeys(isolate, from, KeyCollectionMode::kOwnOnly, ALL_PROPERTIES, GetKeysConversion::kKeepNumbers)); if (!from->HasFastProperties() && target->HasFastProperties() && IsJSObject(*target) && !IsJSGlobalProxy(*target)) { // Convert to slow properties if we're guaranteed to overflow the number of // descriptors. int source_length; if (IsJSGlobalObject(*from)) { source_length = Cast<JSGlobalObject>(*from) ->global_dictionary(kAcquireLoad) ->NumberOfEnumerableProperties(); } else if constexpr (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) { source_length = from->property_dictionary_swiss()->NumberOfEnumerableProperties(); } else { source_length = from->property_dictionary()->NumberOfEnumerableProperties(); } if (source_length > kMaxNumberOfDescriptors) { JSObject::NormalizeProperties(isolate, Cast<JSObject>(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) { DirectHandle<Object> next_key(keys->get(i), isolate); if (!excluded_properties.empty() && HasExcludedProperty(excluded_properties, next_key)) { continue; } // 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). DirectHandle<Object> prop_value; ASSIGN_RETURN_ON_EXCEPTION( isolate, prop_value, Runtime::GetObjectProperty(isolate, from, next_key)); if (use_set) { // 4c ii 2. Let status be ? Set(to, nextKey, propValue, true). DirectHandle<Object> status; ASSIGN_RETURN_ON_EXCEPTION( isolate, status, Runtime::SetObjectProperty(isolate, target, next_key, prop_value, StoreOrigin::kMaybeKeyed, Just(ShouldThrow::kThrowOnError))); } else { // 4a ii 2. Perform ! CreateDataProperty(target, nextKey, propValue). PropertyKey key(isolate, next_key); CHECK(JSReceiver::CreateDataProperty(isolate, target, key, prop_value, Just(kThrowOnError)) .FromJust()); } } } return Just(true); } Tagged<String> JSReceiver::class_name() { ReadOnlyRoots roots = GetReadOnlyRoots(); if (IsJSFunctionOrBoundFunctionOrWrappedFunction(*this)) { return roots.Function_string(); } if (IsJSArgumentsObject(*this)) return roots.Arguments_string(); if (IsJSArray(*this)) return roots.Array_string(); if (IsJSArrayBuffer(*this)) { if (Cast<JSArrayBuffer>(*this)->is_shared()) { return roots.SharedArrayBuffer_string(); } return roots.ArrayBuffer_string(); } if (IsJSArrayIterator(*this)) return roots.ArrayIterator_string(); if (IsJSDate(*this)) return roots.Date_string(); if (IsJSError(*this)) return roots.Error_string(); if (IsJSGeneratorObject(*this)) return roots.Generator_string(); if (IsJSMap(*this)) return roots.Map_string(); if (IsJSMapIterator(*this)) return roots.MapIterator_string(); if (IsJSProxy(*this)) { return map()->is_callable() ? roots.Function_string() : roots.Object_string(); } if (IsJSRegExp(*this)) return roots.RegExp_string(); if (IsJSSet(*this)) return roots.Set_string(); if (IsJSSetIterator(*this)) return roots.SetIterator_string(); if (IsJSTypedArray(*this)) { #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(*this)) { Tagged<Object> value = Cast<JSPrimitiveWrapper>(*this)->value(); if (IsBoolean(value)) return roots.Boolean_string(); if (IsString(value)) return roots.String_string(); if (IsNumber(value)) return roots.Number_string(); if (IsBigInt(value)) return roots.BigInt_string(); if (IsSymbol(value)) return roots.Symbol_string(); if (IsScript(value)) return roots.Script_string(); UNREACHABLE(); } if (IsJSWeakMap(*this)) return roots.WeakMap_string(); if (IsJSWeakSet(*this)) return roots.WeakSet_string(); if (IsShared(*this)) { if (IsJSSharedStruct(*this)) return roots.SharedStruct_string(); if (IsJSSharedArray(*this)) return roots.SharedArray_string(); if (IsJSAtomicsMutex(*this)) return roots.AtomicsMutex_string(); if (IsJSAtomicsCondition(*this)) return roots.AtomicsCondition_string(); // Other shared values are primitives. UNREACHABLE(); } return roots.Object_string(); } namespace { std::pair<MaybeDirectHandle<JSFunction>, DirectHandle<String>> GetConstructorHelper(Isolate* isolate, DirectHandle<JSReceiver> receiver) { // 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 (!IsJSProxy(*receiver) && receiver->map()->new_target_is_base() && !receiver->map()->is_prototype_map()) { DirectHandle<Object> maybe_constructor(receiver->map()->GetConstructor(), isolate); if (IsJSFunction(*maybe_constructor)) { DirectHandle<JSFunction> constructor = Cast<JSFunction>(maybe_constructor); DirectHandle<String> name = JSFunction::GetDebugName(isolate, constructor); if (name->length() != 0 && !name->Equals(ReadOnlyRoots(isolate).Object_string())) { return std::make_pair(indirect_handle(constructor, isolate), name); } } else if (IsFunctionTemplateInfo(*maybe_constructor)) { DirectHandle<FunctionTemplateInfo> function_template = Cast<FunctionTemplateInfo>(maybe_constructor); if (!IsUndefined(function_template->class_name(), isolate)) { return std::make_pair( MaybeHandle<JSFunction>(), handle(Cast<String>(function_template->class_name()), isolate)); } } } bool is_hidden_prototype = false; for (PrototypeIterator it(isolate, receiver, kStartAtReceiver); !it.IsAtEnd(); it.AdvanceIgnoringProxies()) { auto current = PrototypeIterator::GetCurrent<JSReceiver>(it); LookupIterator it_to_string_tag( isolate, receiver, isolate->factory()->to_string_tag_symbol(), current, LookupIterator::OWN_SKIP_INTERCEPTOR); auto maybe_to_string_tag = JSReceiver::GetDataProperty( &it_to_string_tag, AllocationPolicy::kAllocationDisallowed); if (IsString(*maybe_to_string_tag)) { return std::make_pair(MaybeHandle<JSFunction>(), Cast<String>(maybe_to_string_tag)); } // If current object is a hidden prototype then the most accurate name // is the class name of its constructor's template. if (is_hidden_prototype) { DirectHandle<Object> maybe_constructor(current->map()->GetConstructor(), isolate); if (IsFunctionTemplateInfo(*maybe_constructor)) { DirectHandle<FunctionTemplateInfo> function_template = Cast<FunctionTemplateInfo>(maybe_constructor); if (!IsUndefined(function_template->class_name(), isolate)) { return std::make_pair( MaybeHandle<JSFunction>(), handle(Cast<String>(function_template->class_name()), isolate)); } } } // Consider the following example: // // function A() {} // function B() {} // B.prototype = new A(); // B.prototype.constructor = B; // // The constructor name for `B.prototype` must yield "A", so we don't take // "constructor" into account for the receiver itself, but only starting // on the prototype chain. if (!receiver.is_identical_to(current)) { LookupIterator it_constructor( isolate, receiver, isolate->factory()->constructor_string(), current, LookupIterator::OWN_SKIP_INTERCEPTOR); auto maybe_constructor = JSReceiver::GetDataProperty( &it_constructor, AllocationPolicy::kAllocationDisallowed); if (IsJSFunction(*maybe_constructor)) { auto constructor = Cast<JSFunction>(maybe_constructor); auto name = SharedFunctionInfo::DebugName( isolate, direct_handle(constructor->shared(), isolate)); if (name->length() != 0 && !name->Equals(ReadOnlyRoots(isolate).Object_string())) { return std::make_pair(constructor, name); } } } is_hidden_prototype = IsJSGlobalProxy(*current); } return std::make_pair(MaybeHandle<JSFunction>(), handle(receiver->class_name(), isolate)); } } // anonymous namespace // static MaybeDirectHandle<JSFunction> JSReceiver::GetConstructor( Isolate* isolate, DirectHandle<JSReceiver> receiver) { return GetConstructorHelper(isolate, receiver).first; } // static DirectHandle<String> JSReceiver::GetConstructorName( Isolate* isolate, DirectHandle<JSReceiver> receiver) { return GetConstructorHelper(isolate, receiver).second; } // static MaybeDirectHandle<NativeContext> JSReceiver::GetFunctionRealm( DirectHandle<JSReceiver> receiver) { Isolate* isolate = Isolate::Current(); // This is implemented as a loop because it's possible to construct very // long chains of bound functions or proxies where a recursive implementation // would run out of stack space. DisallowGarbageCollection no_gc; Tagged<JSReceiver> current = *receiver; do { DCHECK(current->map()->is_constructor()); InstanceType instance_type = current->map()->instance_type(); if (InstanceTypeChecker::IsJSProxy(instance_type)) { Tagged<JSProxy> proxy = Cast<JSProxy>(current); if (proxy->IsRevoked()) { AllowGarbageCollection allow_allocating_errors; THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kProxyRevoked)); } current = Cast<JSReceiver>(proxy->target()); continue; } if (InstanceTypeChecker::IsJSFunction(instance_type)) { Tagged<JSFunction> function = Cast<JSFunction>(current); return direct_handle(function->native_context(), isolate); } if (InstanceTypeChecker::IsJSBoundFunction(instance_type)) { Tagged<JSBoundFunction> function = Cast<JSBoundFunction>(current); current = function->bound_target_function(); continue; } if (InstanceTypeChecker::IsJSWrappedFunction(instance_type)) { Tagged<JSWrappedFunction> function = Cast<JSWrappedFunction>(current); current = function->wrapped_target_function(); continue; } Tagged<JSObject> object = Cast<JSObject>(current); DCHECK(!IsJSFunction(object)); return object->GetCreationContext(isolate); } while (true); } // static MaybeHandle<NativeContext> JSReceiver::GetContextForMicrotask( DirectHandle<JSReceiver> receiver) { Isolate* isolate = Isolate::Current(); while (IsJSBoundFunction(*receiver) || IsJSProxy(*receiver)) { if (IsJSBoundFunction(*receiver)) { receiver = direct_handle( Cast<JSBoundFunction>(receiver)->bound_target_function(), isolate); } else { DCHECK(IsJSProxy(*receiver)); DirectHandle<Object> target(Cast<JSProxy>(receiver)->target(), isolate); if (!IsJSReceiver(*target)) return MaybeHandle<NativeContext>(); receiver = Cast<JSReceiver>(target); } } if (!IsJSFunction(*receiver)) return MaybeHandle<NativeContext>(); return handle(Cast<JSFunction>(receiver)->native_context(), isolate); } Maybe<PropertyAttributes> JSReceiver::GetPropertyAttributes( LookupIterator* it) { for (;; it->Next()) { switch (it->state()) { case LookupIterator::TRANSITION: case LookupIterator::STRING_LOOKUP_START_OBJECT: UNREACHABLE(); case LookupIterator::JSPROXY: return JSProxy::GetPropertyAttributes(it); case LookupIterator::WASM_OBJECT: return Just(ABSENT); case LookupIterator::INTERCEPTOR: { Maybe<PropertyAttributes> result = JSObject::GetPropertyAttributesWithInterceptor(it); if (result.IsNothing()) return result; if (result.FromJust() != ABSENT) return result; continue; } case LookupIterator::ACCESS_CHECK: if (it->HasAccess()) continue; return JSObject::GetPropertyAttributesWithFailedAccessCheck(it); case LookupIterator::TYPED_ARRAY_INDEX_NOT_FOUND: return Just(ABSENT); case LookupIterator::MODULE_NAMESPACE: { if (JSDeferredModuleNamespace::TriggersEvaluation(it)) { DirectHandle<JSDeferredModuleNamespace> holder = it->GetHolder<JSDeferredModuleNamespace>(); JSDeferredModuleNamespace::EvaluateModuleSync(it->isolate(), holder); RETURN_EXCEPTION_IF_EXCEPTION(it->isolate()); } continue; } case LookupIterator::ACCESSOR: if (IsJSModuleNamespace(*it->GetHolder<Object>())) { return JSModuleNamespace::GetPropertyAttributes(it); } else { return Just(it->property_attributes()); } case LookupIterator::DATA: return Just(it->property_attributes()); case LookupIterator::NOT_FOUND: return Just(ABSENT); } UNREACHABLE(); } } namespace { Tagged<Object> SetHashAndUpdateProperties(Tagged<HeapObject> properties, int hash) { DCHECK_NE(PropertyArray::kNoHashSentinel, hash); DCHECK(PropertyArray::HashField::is_valid(hash)); ReadOnlyRoots roots = GetReadOnlyRoots(); if (properties == roots.empty_fixed_array() || properties == roots.empty_property_array() || properties == roots.empty_property_dictionary() || properties == roots.empty_swiss_property_dictionary()) { return Smi::FromInt(hash); } if (IsPropertyArray(properties)) { Cast<PropertyArray>(properties)->SetHash(hash); DCHECK_LT(0, Cast<PropertyArray>(properties)->length()); return properties; } if (IsGlobalDictionary(properties)) { Cast<GlobalDictionary>(properties)->SetHash(hash); return properties; } DCHECK(IsPropertyDictionary(properties)); Cast<PropertyDictionary>(properties)->SetHash(hash); return properties; } int GetIdentityHashHelper(Tagged<JSReceiver> object) { DisallowGarbageCollection no_gc; Tagged<Object> properties = object->raw_properties_or_hash(); if (IsSmi(properties)) { return Smi::ToInt(properties); } if (IsPropertyArray(properties)) { return Cast<PropertyArray>(properties)->Hash(); } if (IsPropertyDictionary(properties)) { return Cast<PropertyDictionary>(properties)->Hash(); } if (IsGlobalDictionary(properties)) { return Cast<GlobalDictionary>(properties)->Hash(); } #ifdef DEBUG ReadOnlyRoots roots = GetReadOnlyRoots(); DCHECK(properties == roots.empty_fixed_array() || properties == roots.empty_property_dictionary() || properties == roots.empty_swiss_property_dictionary()); #endif return PropertyArray::kNoHashSentinel; } } // namespace void JSReceiver::SetIdentityHash(int hash) { DisallowGarbageCollection no_gc; DCHECK_NE(PropertyArray::kNoHashSentinel, hash); DCHECK(PropertyArray::HashField::is_valid(hash)); Tagged<HeapObject> existing_properties = Cast<HeapObject>(raw_properties_or_hash()); Tagged<Object> new_properties = SetHashAndUpdateProperties(existing_properties, hash); set_raw_properties_or_hash(new_properties, kRelaxedStore); } void JSReceiver::SetProperties(Tagged<HeapObject> properties) { DCHECK_IMPLIES(IsPropertyArray(properties) && Cast<PropertyArray>(properties)->length() == 0, properties == GetReadOnlyRoots().empty_property_array()); DisallowGarbageCollection no_gc; int hash = GetIdentityHashHelper(*this); Tagged<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, kRelaxedStore); } Tagged<Object> JSReceiver::GetIdentityHash() { DisallowGarbageCollection no_gc; int hash = GetIdentityHashHelper(*this); if (hash == PropertyArray::kNoHashSentinel) { return GetReadOnlyRoots().undefined_value(); } return Smi::FromInt(hash); } // static Tagged<Smi> JSReceiver::CreateIdentityHash(Isolate* isolate, Tagged<JSReceiver> key) { DisallowGarbageCollection no_gc; int hash = isolate->GenerateIdentityHash(PropertyArray::HashField::kMax); DCHECK_NE(PropertyArray::kNoHashSentinel, hash); key->SetIdentityHash(hash); return Smi::FromInt(hash); } Tagged<Smi> JSReceiver::GetOrCreateIdentityHash(Isolate* isolate) { DisallowGarbageCollection no_gc; int hash = GetIdentityHashHelper(*this); if (hash != PropertyArray::kNoHashSentinel) { return Smi::FromInt(hash); } return JSReceiver::CreateIdentityHash(isolate, *this); } void JSReceiver::DeleteNormalizedProperty(DirectHandle<JSReceiver> object, InternalIndex entry) { DCHECK(!object->HasFastProperties()); Isolate* isolate = Isolate::Current(); DCHECK(entry.is_found()); if (IsJSGlobalObject(*object)) { // If we have a global object, invalidate the cell and remove it from the // global object's dictionary. DirectHandle<GlobalDictionary> dictionary( Cast<JSGlobalObject>(*object)->global_dictionary(kAcquireLoad), isolate); DirectHandle<PropertyCell> cell(dictionary->CellAt(entry), isolate); DirectHandle<GlobalDictionary> new_dictionary = GlobalDictionary::DeleteEntry(isolate, dictionary, entry); Cast<JSGlobalObject>(*object)->set_global_dictionary(*new_dictionary, kReleaseStore); cell->ClearAndInvalidate(isolate); } else { if constexpr (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) { DirectHandle<SwissNameDictionary> dictionary( object->property_dictionary_swiss(), isolate); dictionary = SwissNameDictionary::DeleteEntry(isolate, dictionary, entry); object->SetProperties(*dictionary); } else { DirectHandle<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 (IsJSProxy(*it->GetReceiver())) { if (it->state() != LookupIterator::NOT_FOUND) { DCHECK_EQ(LookupIterator::DATA, it->state()); DCHECK(it->name()->IsAnyPrivate()); it->Delete(); } return Just(true); } for (;; it->Next()) { switch (it->state()) { case LookupIterator::JSPROXY: case LookupIterator::TRANSITION: case LookupIterator::STRING_LOOKUP_START_OBJECT: UNREACHABLE(); case LookupIterator::WASM_OBJECT: RETURN_FAILURE(isolate, kThrowOnError, NewTypeError(MessageTemplate::kWasmObjectsAreOpaque)); case LookupIterator::ACCESS_CHECK: if (it->HasAccess()) continue; RETURN_ON_EXCEPTION_VALUE( isolate, isolate->ReportFailedAccessCheck(it->GetHolder<JSObject>()), Nothing<bool>()); UNREACHABLE(); case LookupIterator::INTERCEPTOR: { // TODO(https://crbug.com/348660658): replace language mode // parameter with Maybe<ShouldThrow> and use GetShouldThrow() here. ShouldThrow should_throw = is_sloppy(language_mode) ? kDontThrow : kThrowOnError; InterceptorResult result; if (!JSObject::DeletePropertyWithInterceptor(it, should_throw) .To(&result)) { // An exception was thrown in the interceptor. Propagate. return Nothing<bool>(); } switch (result) { case InterceptorResult::kFalse: { // Throw TypeError if necessary in case the callback failed // to delete the property. RETURN_FAILURE( isolate, should_throw, NewTypeError(MessageTemplate::kStrictCannotDeleteProperty, it->GetName(), it->GetReceiver())); } case InterceptorResult::kTrue: return Just(true); case InterceptorResult::kNotIntercepted: // Proceed lookup. continue; } UNREACHABLE(); } case LookupIterator::TYPED_ARRAY_INDEX_NOT_FOUND: return Just(true); case LookupIterator::MODULE_NAMESPACE: { if (JSDeferredModuleNamespace::TriggersEvaluation(it)) { DirectHandle<JSDeferredModuleNamespace> holder = it->GetHolder<JSDeferredModuleNamespace>(); JSDeferredModuleNamespace::EvaluateModuleSync(it->isolate(), holder); RETURN_EXCEPTION_IF_EXCEPTION(it->isolate()); } continue; } case LookupIterator::DATA: case LookupIterator::ACCESSOR: { DirectHandle<JSObject> holder = it->GetHolder<JSObject>(); if (!it->IsConfigurable() || (IsJSTypedArray(*holder) && it->IsElement(*holder))) { // TODO(https://crbug.com/348660658): replace language mode // parameter with Maybe<ShouldThrow> and use GetShouldThrow() here. ShouldThrow should_throw = is_sloppy(language_mode) ? kDontThrow : kThrowOnError; // Fail if the property is not configurable or if the property is a // TypedArray element. RETURN_FAILURE( isolate, should_throw, NewTypeError(MessageTemplate::kStrictCannotDeleteProperty, it->GetName(), it->GetReceiver())); } it->Delete(); return Just(true); } case LookupIterator::NOT_FOUND: return Just(true); } UNREACHABLE(); } } Maybe<bool> JSReceiver::DeleteElement(Isolate* isolate, DirectHandle<JSReceiver> object, uint32_t index, LanguageMode language_mode) { LookupIterator it(isolate, object, index, object, LookupIterator::OWN); return DeleteProperty(&it, language_mode); } Maybe<bool> JSReceiver::DeleteProperty(Isolate* isolate, DirectHandle<JSReceiver> object, DirectHandle<Name> name, LanguageMode language_mode) { LookupIterator it(isolate, object, name, object, LookupIterator::OWN); return DeleteProperty(&it, language_mode); } Maybe<bool> JSReceiver::DeletePropertyOrElement(Isolate* isolate, DirectHandle<JSReceiver> object, DirectHandle<Name> name, LanguageMode language_mode) { return DeletePropertyOrElement(isolate, object, PropertyKey(isolate, name), language_mode); } Maybe<bool> JSReceiver::DeletePropertyOrElement(Isolate* isolate, DirectHandle<JSReceiver> object, PropertyKey key, LanguageMode language_mode) { LookupIterator it(isolate, object, key, object, LookupIterator::OWN); return DeleteProperty(&it, language_mode); } // ES6 19.1.2.4 // static Tagged<Object> JSReceiver::DefineProperty(Isolate* isolate, DirectHandle<Object> object, DirectHandle<Object> key, Handle<Object> attributes) { // 1. If Type(O) is not Object, throw a TypeError exception. if (!IsJSReceiver(*object)) { DirectHandle<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, Cast<JSAny>(attributes), &desc)) { return ReadOnlyRoots(isolate).exception(); } // 6. Let success be DefinePropertyOrThrow(O,key, desc). Maybe<bool> success = DefineOwnProperty(isolate, Cast<JSReceiver>(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 MaybeDirectHandle<Object> JSReceiver::DefineProperties( Isolate* isolate, DirectHandle<Object> object, DirectHandle<Object> properties) { // 1. If Type(O) is not Object, throw a TypeError exception. if (!IsJSReceiver(*object)) { DirectHandle<String> fun_name = isolate->factory()->InternalizeUtf8String("Object.defineProperties"); THROW_NEW_ERROR( isolate, NewTypeError(MessageTemplate::kCalledOnNonObject, fun_name)); } // 2. Let props be ToObject(Properties). // 3. ReturnIfAbrupt(props). DirectHandle<JSReceiver> props; ASSIGN_RETURN_ON_EXCEPTION(isolate, props, Object::ToObject(isolate, properties)); // 4. Let keys be props.[[OwnPropertyKeys]](). // 5. ReturnIfAbrupt(keys). DirectHandle<FixedArray> keys; ASSIGN_RETURN_ON_EXCEPTION( isolate, keys, KeyAccumulator::GetKeys(isolate, props, KeyCollectionMode::kOwnOnly, ALL_PROPERTIES)); // 6. Let descriptors be an empty List.s 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) { DirectHandle<JSAny> next_key(Cast<JSAny>(keys->get(i)), isolate); // 7a. Let propDesc be props.[[GetOwnProperty]](nextKey). // 7b. ReturnIfAbrupt(propDesc). PropertyKey key(isolate, next_key); LookupIterator it(isolate, props, key, LookupIterator::OWN); Maybe<PropertyAttributes> maybe = JSReceiver::GetPropertyAttributes(&it); if (maybe.IsNothing()) return MaybeDirectHandle<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<JSAny> desc_obj; ASSIGN_RETURN_ON_EXCEPTION(isolate, desc_obj, Cast<JSAny>(Object::GetProperty(&it))); // 7c iii. Let desc be ToPropertyDescriptor(descObj). bool success = PropertyDescriptor::ToPropertyDescriptor( isolate, desc_obj, &descriptors[descriptors_index]); // 7c iv. ReturnIfAbrupt(desc). if (!success) return MaybeDirectHandle<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, Cast<JSReceiver>(object), desc->name(), desc, Just(kThrowOnError)); // 8d. ReturnIfAbrupt(status). if (status.IsNothing()) return MaybeDirectHandle<Object>(); CHECK(status.FromJust()); } // 9. Return o. return object; } // static Maybe<bool> JSReceiver::DefineOwnProperty(Isolate* isolate, DirectHandle<JSReceiver> object, DirectHandle<Object> key, PropertyDescriptor* desc, Maybe<ShouldThrow> should_throw) { if (IsJSArray(*object)) { return JSArray::DefineOwnProperty(isolate, Cast<JSArray>(object), key, desc, should_throw); } if (IsJSProxy(*object)) { return JSProxy::DefineOwnProperty(isolate, Cast<JSProxy>(object), key, desc, should_throw); } if (IsJSTypedArray(*object)) { return JSTypedArray::DefineOwnProperty(isolate, Cast<JSTypedArray>(object), key, desc, should_throw); } if (IsJSModuleNamespace(*object)) { return JSModuleNamespace::DefineOwnProperty( isolate, Cast<JSModuleNamespace>(object), key, desc, should_throw); } if (IsWasmObject(*object)) { RETURN_FAILURE(isolate, kThrowOnError, NewTypeError(MessageTemplate::kWasmObjectsAreOpaque)); } if (IsAlwaysSharedSpaceJSObject(*object)) { return AlwaysSharedSpaceJSObject::DefineOwnProperty( isolate, Cast<AlwaysSharedSpaceJSObject>(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, Cast<JSObject>(object), key, desc, should_throw); } // static Maybe<bool> JSReceiver::OrdinaryDefineOwnProperty( Isolate* isolate, DirectHandle<JSObject> object, DirectHandle<Object> key, PropertyDescriptor* desc, Maybe<ShouldThrow> should_throw) { DCHECK(IsName(*key) || IsNumber(*key)); // |key| is a PropertyKey. PropertyKey lookup_key(isolate, key); return OrdinaryDefineOwnProperty(isolate, object, lookup_key, desc, should_throw); } namespace { MaybeHandle<JSAny> GetPropertyWithInterceptorInternal( LookupIterator* it, DirectHandle<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->has_getter()) { return isolate->factory()->undefined_value(); } DirectHandle<JSObject> holder = it->GetHolder<JSObject>(); PropertyCallbackArguments args(isolate, it->GetHolderForApi()); DirectHandle<JSAny> result; if (it->IsElement(*holder)) { result = args.CallIndexedGetter(isolate, interceptor, it->array_index()); } else { result = args.CallNamedGetter(isolate, interceptor, it->name()); } // An exception was thrown in the interceptor. Propagate. RETURN_VALUE_IF_EXCEPTION_DETECTOR(isolate, args, kNullMaybeHandle); if (result.is_null()) return isolate->factory()->undefined_value(); *done = true; args.AcceptSideEffects(); // Rebox handle before return return handle(*result, isolate); } Maybe<PropertyAttributes> GetPropertyAttributesWithInterceptorInternal( LookupIterator* it, DirectHandle<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); DirectHandle<JSObject> holder = it->GetHolder<JSObject>(); DCHECK_IMPLIES(!it->IsElement(*holder) && IsSymbol(*it->name()), interceptor->can_intercept_symbols()); PropertyCallbackArguments args(isolate, it->GetHolderForApi()); if (interceptor->has_query()) { DirectHandle<Object> result; if (it->IsElement(*holder)) { result = args.CallIndexedQuery(isolate, interceptor, it->array_index()); } else { result = args.CallNamedQuery(isolate, interceptor, it->name()); } // An exception was thrown in the interceptor. Propagate. RETURN_VALUE_IF_EXCEPTION_DETECTOR(isolate, args, Nothing<PropertyAttributes>()); if (!result.is_null()) { int32_t value; CHECK(Object::ToInt32(*result, &value)); DCHECK_IMPLIES((value & ~PropertyAttributes::ALL_ATTRIBUTES_MASK) != 0, value == PropertyAttributes::ABSENT); // In case of absent property side effects are not allowed. // TODO(ishell): PropertyAttributes::ABSENT is not exposed in the Api, // so it can't be officially returned. We should fix the tests instead. if (value != PropertyAttributes::ABSENT) { args.AcceptSideEffects(); } return Just(static_cast<PropertyAttributes>(value)); } } else if (interceptor->has_getter()) { // TODO(verwaest): Use GetPropertyWithInterceptor? DirectHandle<Object> result; if (it->IsElement(*holder)) { result = args.CallIndexedGetter(isolate, interceptor, it->array_index()); } else { result = args.CallNamedGetter(isolate, interceptor, it->name()); } // An exception was thrown in the interceptor. Propagate. RETURN_VALUE_IF_EXCEPTION_DETECTOR(isolate, args, Nothing<PropertyAttributes>()); if (!result.is_null()) { args.AcceptSideEffects(); return Just(DONT_ENUM); } } return Just(ABSENT); } Maybe<InterceptorResult> SetPropertyWithInterceptorInternal( LookupIterator* it, DirectHandle<InterceptorInfo> interceptor, Maybe<ShouldThrow> should_throw, DirectHandle<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->has_setter()) { return Just(InterceptorResult::kNotIntercepted); } DirectHandle<JSObject> holder = it->GetHolder<JSObject>(); PropertyCallbackArguments args(isolate, it->GetHolderForApi(), should_throw); v8::Intercepted intercepted = it->IsElement(*holder) ? args.CallIndexedSetter(isolate, interceptor, it->array_index(), value) : args.CallNamedSetter(isolate, interceptor, it->name(), value); return args.GetBooleanReturnValue(isolate, intercepted, "Setter"); } Maybe<InterceptorResult> DefinePropertyWithInterceptorInternal( LookupIterator* it, DirectHandle<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->has_definer()) { return Just(InterceptorResult::kNotIntercepted); } DirectHandle<JSObject> holder = it->GetHolder<JSObject>(); std::unique_ptr<v8::PropertyDescriptor> descriptor( new v8::PropertyDescriptor()); if (PropertyDescriptor::IsAccessorDescriptor(desc)) { DirectHandle<Object> getter = desc->get(); if (!getter.is_null() && IsFunctionTemplateInfo(*getter)) { ASSIGN_RETURN_ON_EXCEPTION( isolate, getter, ApiNatives::InstantiateFunction(isolate, Cast<FunctionTemplateInfo>(getter), MaybeDirectHandle<Name>())); } DirectHandle<Object> setter = desc->set(); if (!setter.is_null() && IsFunctionTemplateInfo(*setter)) { ASSIGN_RETURN_ON_EXCEPTION( isolate, setter, ApiNatives::InstantiateFunction(isolate, Cast<FunctionTemplateInfo>(setter), MaybeDirectHandle<Name>())); } descriptor.reset(new v8::PropertyDescriptor(v8::Utils::ToLocal(getter), v8::Utils::ToLocal(setter))); } 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()); } PropertyCallbackArguments args(isolate, it->GetHolderForApi(), should_throw); v8::Intercepted intercepted = it->IsElement(*holder) ? args.CallIndexedDefiner(isolate, interceptor, it->array_index(), *descriptor) : args.CallNamedDefiner(isolate, interceptor, it->name(), *descriptor); return args.GetBooleanReturnValue(isolate, intercepted, "Definer"); } } // namespace // ES6 9.1.6.1 // static Maybe<bool> JSReceiver::OrdinaryDefineOwnProperty( Isolate* isolate, DirectHandle<JSObject> object, const PropertyKey& key, PropertyDescriptor* desc, Maybe<ShouldThrow> should_throw) { LookupIterator it(isolate, object, key, LookupIterator::OWN); // Deal with access checks first. while (it.state() == LookupIterator::ACCESS_CHECK) { if (!it.HasAccess()) { RETURN_ON_EXCEPTION_VALUE( isolate, isolate->ReportFailedAccessCheck(it.GetHolder<JSObject>()), Nothing<bool>()); UNREACHABLE(); } it.Next(); } // 1. Let current be O.[[GetOwnProperty]](P). // 2. ReturnIfAbrupt(current). PropertyDescriptor current; MAYBE_RETURN(GetOwnPropertyDescriptor(&it, ¤t), Nothing<bool>()); // 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 because // GetOwnPropertyDescriptor can have side effects, namely: // - Interceptors // - Accessors (which might change the holder's map) it.Restart(); // Skip over the access check after restarting -- we've already checked it. while (it.state() == LookupIterator::ACCESS_CHECK) { DCHECK(it.HasAccess()); it.Next(); } // Handle interceptor. if (it.state() == LookupIterator::INTERCEPTOR) { if (it.HolderIsReceiverOrHiddenPrototype()) { InterceptorResult result; if (!DefinePropertyWithInterceptorInternal(&it, it.GetInterceptor(), should_throw, desc) .To(&result)) { // An exception was thrown in the interceptor. Propagate. return Nothing<bool>(); } switch (result) { case InterceptorResult::kFalse: { // Throw TypeError if necessary in case the callback failed // to define the property. return Object::CannotCreateProperty(isolate, it.GetReceiver(), it.GetName(), should_throw); } case InterceptorResult::kTrue: return Just(true); case InterceptorResult::kNotIntercepted: // Proceed lookup. break; } // We need to restart the lookup in case the interceptor ran with side // effects. it.Restart(); } } // 3. Let extensible be the value of the [[Extensible]] internal slot of O. bool extensible = JSObject::IsExtensible(isolate, object); return ValidateAndApplyPropertyDescriptor(isolate, &it, extensible, desc, ¤t, should_throw, {}); } // ES6 9.1.6.2 // static Maybe<bool> JSReceiver::IsCompatiblePropertyDescriptor( Isolate* isolate, bool extensible, PropertyDescriptor* desc, PropertyDescriptor* current, DirectHandle<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); } // https://tc39.es/ecma262/#sec-validateandapplypropertydescriptor // static Maybe<bool> JSReceiver::ValidateAndApplyPropertyDescriptor( Isolate* isolate, LookupIterator* it, bool extensible, PropertyDescriptor* desc, PropertyDescriptor* current, Maybe<ShouldThrow> should_throw, DirectHandle<Name> property_name) { // We either need a LookupIterator, or a property name. DCHECK((it == nullptr) != property_name.is_null()); 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_EQ(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); DirectHandle<Object> value( desc->has_value() ? desc->value() : Cast<Object>(isolate->factory()->undefined_value())); MaybeDirectHandle<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); DirectHandle<Object> getter( desc->has_get() ? desc->get() : Cast<Object>(isolate->factory()->null_value())); DirectHandle<Object> setter( desc->has_set() ? desc->set() : Cast<Object>(isolate->factory()->null_value())); MaybeDirectHandle<Object> result = JSObject::DefineOwnAccessorIgnoreAttributes(it, getter, setter, desc->ToAttributes()); if (result.is_null()) return Nothing<bool>(); } } // 2e. Return true. return Just(true); } // 3. If every field in Desc is absent, return true. (This also has a shortcut // not in the spec: if every field value matches the current value, return.) if ((!desc->has_enumerable() || desc->enumerable() == current->enumerable()) && (!desc->has_configurable() || desc->configurable() == current->configurable()) && !desc->has_value() && (!desc->has_writable() || (current->has_writable() && current->writable() == desc->writable())) && (!desc->has_get() || (current->has_get() && Object::SameValue(*current->get(), *desc->get()))) && (!desc->has_set() || (current->has_set() && Object::SameValue(*current->set(), *desc->set())))) { return Just(true); } // 4. If current.[[Configurable]] is false, then if (!current->configurable()) { // 4a. If Desc.[[Configurable]] is present and its value is true, return // false. if (desc->has_configurable() && desc->configurable()) { RETURN_FAILURE( isolate, GetShouldThrow(isolate, should_throw), NewTypeError(MessageTemplate::kRedefineDisallowed, it != nullptr ? it->GetName() : property_name)); } // 4b. If Desc.[[Enumerable]] is present and // ! SameValue(Desc.[[Enumerable]], current.[[Enumerable]]) is false, return // false. 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); // 5. If ! IsGenericDescriptor(Desc) is true, no further validation is // required. if (desc_is_generic_descriptor) { // Nothing to see here. // 6. Else if ! SameValue(!IsDataDescriptor(current), // !IsDataDescriptor(Desc)) is false, the } else if (current_is_data_descriptor != desc_is_data_descriptor) { // 6a. If current.[[Configurable]] is false, return false. if (!current->configurable()) { RETURN_FAILURE( isolate, GetShouldThrow(isolate, should_throw), NewTypeError(MessageTemplate::kRedefineDisallowed, it != nullptr ? it->GetName() : property_name)); } // 6b. If IsDataDescriptor(current) is true, then: if (current_is_data_descriptor) { // 6b 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 9 } else { // 6c 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 9 } // 7. Else if IsDataDescriptor(current) and IsDataDescriptor(Desc) are both // true, then: } else if (current_is_data_descriptor && desc_is_data_descriptor) { // 7a. If current.[[Configurable]] is false and current.[[Writable]] is // false, then if (!current->configurable() && !current->writable()) { // 7a i. If Desc.[[Writable]] is present and Desc.[[Writable]] is true, // return false. if (desc->has_writable() && desc->writable()) { RETURN_FAILURE( isolate, GetShouldThrow(isolate, should_throw), NewTypeError(MessageTemplate::kRedefineDisallowed, it != nullptr ? it->GetName() : property_name)); } // 7a ii. If Desc.[[Value]] is present and SameValue(Desc.[[Value]], // current.[[Value]]) is false, return false. if (desc->has_value()) { // We'll succeed applying the property, but the value is already the // same and the property is read-only, so skip actually writing the // property. Otherwise we may try to e.g., write to frozen elements. if (Object::SameValue(*desc->value(), *current->value())) return Just(true); RETURN_FAILURE( isolate, GetShouldThrow(isolate, should_throw), NewTypeError(MessageTemplate::kRedefineDisallowed, it != nullptr ? it->GetName() : property_name)); } } } else { // 8. Else, // 8a. Assert: ! IsAccessorDescriptor(current) and // ! IsAccessorDescriptor(Desc) are both true. DCHECK(PropertyDescriptor::IsAccessorDescriptor(current) && desc_is_accessor_descriptor); // 8b. If current.[[Configurable]] is false, then: if (!current->configurable()) { // 8a i. If Desc.[[Set]] is present and SameValue(Desc.[[Set]], // current.[[Set]]) is false, return false. if (desc->has_set() && !Object::SameValue(*desc->set(), *current->set())) { RETURN_FAILURE( isolate, GetShouldThrow(isolate, should_throw), NewTypeError(MessageTemplate::kRedefineDisallowed, it != nullptr ? it->GetName() : property_name)); } // 8a ii. If Desc.[[Get]] is present and SameValue(Desc.[[Get]], // current.[[Get]]) is false, return false. if (desc->has_get() && !Object::SameValue(*desc->get(), *current->get())) { RETURN_FAILURE( isolate, GetShouldThrow(isolate, should_throw), NewTypeError(MessageTemplate::kRedefineDisallowed, it != nullptr ? it->GetName() : property_name)); } } } // 9. If O is not undefined, then: if (it != nullptr) { // 9a. 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)); } DirectHandle<Object> value( desc->has_value() ? desc->value() : current->has_value() ? current->value() : Cast<Object>(isolate->factory()->undefined_value())); return JSObject::DefineOwnPropertyIgnoreAttributes( it, value, attrs, should_throw, JSObject::DONT_FORCE_FIELD, EnforceDefineSemantics::kSet, StoreOrigin::kNamed, current->has_value() ? current->value() : MaybeDirectHandle<Object>()); } else { DCHECK(desc_is_accessor_descriptor || (desc_is_generic_descriptor && PropertyDescriptor::IsAccessorDescriptor(current))); DirectHandle<Object> getter( desc->has_get() ? desc->get() : current->has_get() ? current->get() : Cast<Object>(isolate->factory()->null_value())); DirectHandle<Object> setter( desc->has_set() ? desc->set() : current->has_set() ? current->set() : Cast<Object>(isolate->factory()->null_value())); MaybeDirectHandle<Object> result = JSObject::DefineOwnAccessorIgnoreAttributes(it, getter, setter, attrs); if (result.is_null()) return Nothing<bool>(); } } // 10. Return true. return Just(true); } // static Maybe<bool> JSReceiver::CreateDataProperty(Isolate* isolate, DirectHandle<JSReceiver> object, DirectHandle<Name> key, DirectHandle<Object> value, Maybe<ShouldThrow> should_throw) { return CreateDataProperty(isolate, object, PropertyKey(isolate, key), value, should_throw); } // static Maybe<bool> JSReceiver::CreateDataProperty(Isolate* isolate, DirectHandle<JSAny> object, PropertyKey key, DirectHandle<Object> value, Maybe<ShouldThrow> should_throw) { if (!IsJSReceiver(*object)) { return Object::CannotCreateProperty(isolate, object, key.GetName(isolate), should_throw); } return CreateDataProperty(isolate, Cast<JSReceiver>(object), key, value, should_throw); } // static Maybe<bool> JSReceiver::CreateDataProperty(Isolate* isolate, DirectHandle<JSReceiver> object, PropertyKey key, DirectHandle<Object> value, Maybe<ShouldThrow> should_throw) { if (IsJSObject(*object)) { return JSObject::CreateDataProperty(isolate, Cast<JSObject>(object), key, value, should_throw); // Shortcut. } PropertyDescriptor new_desc; new_desc.set_value(Cast<JSAny>(value)); new_desc.set_writable(true); new_desc.set_enumerable(true); new_desc.set_configurable(true); return JSReceiver::DefineOwnProperty(isolate, object, key.GetName(isolate), &new_desc, should_throw); } // static Maybe<bool> JSReceiver::AddPrivateField(LookupIterator* it, DirectHandle<Object> value, Maybe<ShouldThrow> should_throw) { DirectHandle<JSReceiver> receiver = Cast<JSReceiver>(it->GetReceiver()); DCHECK(!IsAlwaysSharedSpaceJSObject(*receiver)); Isolate* isolate = it->isolate(); DCHECK(it->GetName()->IsAnyPrivateName()); DirectHandle<Symbol> symbol = Cast<Symbol>(it->GetName()); switch (it->state()) { case LookupIterator::JSPROXY: { PropertyDescriptor new_desc; new_desc.set_value(Cast<JSAny>(value)); new_desc.set_writable(true); new_desc.set_enumerable(true); new_desc.set_configurable(true); return JSProxy::SetPrivateSymbol(isolate, Cast<JSProxy>(receiver), symbol, &new_desc, should_throw); } case LookupIterator::WASM_OBJECT: RETURN_FAILURE(isolate, kThrowOnError, NewTypeError(MessageTemplate::kWasmObjectsAreOpaque)); case LookupIterator::MODULE_NAMESPACE: case LookupIterator::DATA: case LookupIterator::INTERCEPTOR: case LookupIterator::ACCESSOR: case LookupIterator::TYPED_ARRAY_INDEX_NOT_FOUND: case LookupIterator::STRING_LOOKUP_START_OBJECT: UNREACHABLE(); case LookupIterator::ACCESS_CHECK: { if (!it->HasAccess()) { RETURN_ON_EXCEPTION_VALUE( isolate, it->isolate()->ReportFailedAccessCheck(it->GetHolder<JSObject>()), Nothing<bool>()); UNREACHABLE(); } break; } case LookupIterator::TRANSITION: case LookupIterator::NOT_FOUND: break; } return Object::TransitionAndWriteDataProperty(it, value, NONE, should_throw, StoreOrigin::kMaybeKeyed); } // static Maybe<bool> JSReceiver::GetOwnPropertyDescriptor( Isolate* isolate, DirectHandle<JSReceiver> object, DirectHandle<Object> key, PropertyDescriptor* desc) { DCHECK(IsName(*key) || IsNumber(*key)); // |key| is a PropertyKey. PropertyKey lookup_key(isolate, key); LookupIterator it(isolate, object, lookup_key, LookupIterator::OWN); return GetOwnPropertyDescriptor(&it, desc); } namespace { Maybe<bool> GetPropertyDescriptorWithInterceptor(LookupIterator* it, PropertyDescriptor* desc) { DirectHandle<InterceptorInfo> interceptor; while (it->state() == LookupIterator::ACCESS_CHECK) { if (it->HasAccess()) { it->Next(); } else { interceptor = it->GetInterceptorForFailedAccessCheck(); if (interceptor.is_null()) { it->Restart(); return Just(false); } CHECK(!interceptor.is_null()); break; } } if (it->state() == LookupIterator::INTERCEPTOR) { interceptor = it->GetInterceptor(); } if (interceptor.is_null()) return Just(false); Isolate* isolate = it->isolate(); if (!interceptor->has_descriptor()) return Just(false); Handle<JSAny> result; DirectHandle<JSObject> holder = it->GetHolder<JSObject>(); PropertyCallbackArguments args(isolate, it->GetHolderForApi()); if (it->IsElement(*holder)) { result = args.CallIndexedDescriptor(isolate, interceptor, it->array_index()); } else { result = args.CallNamedDescriptor(isolate, interceptor, it->name()); } // An exception was thrown in the interceptor. Propagate. RETURN_VALUE_IF_EXCEPTION_DETECTOR(isolate, args, Nothing<bool>()); if (!result.is_null()) { // Request was successfully intercepted, try to set the property // descriptor. args.AcceptSideEffects(); bool is_descriptor = PropertyDescriptor::ToPropertyDescriptor(isolate, result, desc); return is_descriptor ? Just(true) : Nothing<bool>(); } 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() && IsJSProxy(*it->GetHolder<JSReceiver>())) { 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_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 && IsAccessorPair(*it->GetAccessors()); if (!is_accessor_pair) { // 5a. Set D.[[Value]] to the value of X's [[Value]] attribute. DirectHandle<JSAny> value; if (!Cast<JSAny>(Object::GetProperty(it)).ToHandle(&value)) { DCHECK(isolate->has_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 auto accessors = Cast<AccessorPair>(it->GetAccessors()); DirectHandle<NativeContext> holder_realm( it->GetHolder<JSReceiver>()->GetCreationContext().value(), isolate); // 6a. Set D.[[Get]] to the value of X's [[Get]] attribute. desc->set_get(AccessorPair::GetComponent(isolate, holder_realm, accessors, ACCESSOR_GETTER)); // 6b. Set D.[[Set]] to the value of X's [[Set]] attribute. desc->set_set(AccessorPair::GetComponent(isolate, holder_realm, 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(Isolate* isolate, DirectHandle<JSReceiver> receiver, IntegrityLevel level, ShouldThrow should_throw) { DCHECK(level == SEALED || level == FROZEN); if (IsJSObject(*receiver)) { DirectHandle<JSObject> object = Cast<JSObject>(receiver); if (!object->HasSloppyArgumentsElements() && !IsJSModuleNamespace(*object)) { // Fast path. // Prevent memory leaks by not adding unnecessary transitions. Maybe<bool> test = JSObject::TestIntegrityLevel(isolate, object, level); MAYBE_RETURN(test, Nothing<bool>()); if (test.FromJust()) return test; if (level == SEALED) { return JSObject::PreventExtensionsWithTransition<SEALED>( isolate, object, should_throw); } else { return JSObject::PreventExtensionsWithTransition<FROZEN>( isolate, object, should_throw); } } } MAYBE_RETURN(JSReceiver::PreventExtensions(isolate, receiver, should_throw), Nothing<bool>()); DirectHandle<FixedArray> keys; ASSIGN_RETURN_ON_EXCEPTION(isolate, keys, JSReceiver::OwnPropertyKeys(isolate, receiver)); 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) { DirectHandle<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) { DirectHandle<Object> key(keys->get(i), isolate); PropertyDescriptor current_desc; Maybe<bool> owned = JSReceiver::GetOwnPropertyDescriptor( isolate, receiver, key, ¤t_desc); MAYBE_RETURN(owned, Nothing<bool>()); if (owned.FromJust()) { PropertyDescriptor desc = PropertyDescriptor::IsAccessorDescriptor(¤t_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(Isolate* isolate, DirectHandle<JSReceiver> receiver, PropertyAttributes level) { DCHECK(level == SEALED || level == FROZEN); Maybe<bool> extensible = JSReceiver::IsExtensible(isolate, receiver); MAYBE_RETURN(extensible, Nothing<bool>()); if (extensible.FromJust()) return Just(false); DirectHandle<FixedArray> keys; ASSIGN_RETURN_ON_EXCEPTION(isolate, keys, JSReceiver::OwnPropertyKeys(isolate, receiver)); for (int i = 0; i < keys->length(); ++i) { DirectHandle<Object> key(keys->get(i), isolate); PropertyDescriptor current_desc; Maybe<bool> owned = JSReceiver::GetOwnPropertyDescriptor( isolate, receiver, key, ¤t_desc); MAYBE_RETURN(owned, Nothing<bool>()); if (owned.FromJust()) { if (current_desc.configurable()) return Just(false); if (level == FROZEN && PropertyDescriptor::IsDataDescriptor(¤t_desc) && current_desc.writable()) { return Just(false); } } } return Just(true); } } // namespace Maybe<bool> JSReceiver::TestIntegrityLevel(Isolate* isolate, DirectHandle<JSReceiver> receiver, IntegrityLevel level) { if (!IsCustomElementsReceiverMap(receiver->map())) { return JSObject::TestIntegrityLevel(isolate, Cast<JSObject>(receiver), level); } return GenericTestIntegrityLevel(isolate, receiver, level); } Maybe<bool> JSReceiver::PreventExtensions(Isolate* isolate, DirectHandle<JSReceiver> object, ShouldThrow should_throw) { if (IsJSProxy(*object)) { return JSProxy::PreventExtensions(Cast<JSProxy>(object), should_throw); } if (IsWasmObject(*object)) { RETURN_FAILURE(isolate, kThrowOnError, NewTypeError(MessageTemplate::kWasmObjectsAreOpaque)); } DCHECK(IsJSObject(*object)); return JSObject::PreventExtensions(isolate, Cast<JSObject>(object), should_throw); } Maybe<bool> JSReceiver::IsExtensible(Isolate* isolate, DirectHandle<JSReceiver> object) { if (IsJSProxy(*object)) { return JSProxy::IsExtensible(Cast<JSProxy>(object)); } if (IsWasmObject(*object)) { return Just(false); } return Just(JSObject::IsExtensible(isolate, Cast<JSObject>(object))); } // static template <template <typename> typename HandleType> requires( std::is_convertible_v<HandleType<JSReceiver>, DirectHandle<JSReceiver>>) typename HandleType<Object>::MaybeType JSReceiver::ToPrimitive( Isolate* isolate, HandleType<JSReceiver> receiver, ToPrimitiveHint hint) { DirectHandle<Object> exotic_to_prim; ASSIGN_RETURN_ON_EXCEPTION( isolate, exotic_to_prim, Object::GetMethod(isolate, receiver, isolate->factory()->to_primitive_symbol())); if (!IsUndefined(*exotic_to_prim, isolate)) { DirectHandle<Object> hint_string = isolate->factory()->ToPrimitiveHintString(hint); HandleType<Object> result; ASSIGN_RETURN_ON_EXCEPTION( isolate, result, Execution::Call(isolate, exotic_to_prim, receiver, {&hint_string, 1})); if (IsPrimitive(*result)) return result; THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kCannotConvertToPrimitive)); } return OrdinaryToPrimitive<HandleType>( isolate, receiver, (hint == ToPrimitiveHint::kString) ? OrdinaryToPrimitiveHint::kString : OrdinaryToPrimitiveHint::kNumber); } template MaybeDirectHandle<Object> JSReceiver::ToPrimitive( Isolate* isolate, DirectHandle<JSReceiver> receiver, ToPrimitiveHint hint); template MaybeIndirectHandle<Object> JSReceiver::ToPrimitive( Isolate* isolate, IndirectHandle<JSReceiver> receiver, ToPrimitiveHint hint); // static template <template <typename> typename HandleType> requires(std::is_convertible_v<HandleType<Object>, DirectHandle<Object>>) typename HandleType<Object>::MaybeType JSReceiver::OrdinaryToPrimitive( Isolate* isolate, DirectHandle<JSReceiver> receiver, OrdinaryToPrimitiveHint hint) { DirectHandle<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 (DirectHandle<String> name : method_names) { DirectHandle<Object> method; ASSIGN_RETURN_ON_EXCEPTION( isolate, method, JSReceiver::GetProperty(isolate, receiver, name)); if (IsCallable(*method)) { HandleType<Object> result; ASSIGN_RETURN_ON_EXCEPTION( isolate, result, Execution::Call(isolate, method, receiver, {})); if (IsPrimitive(*result)) return result; } } THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kCannotConvertToPrimitive)); } V8_WARN_UNUSED_RESULT Maybe<bool> FastGetOwnValuesOrEntries( Isolate* isolate, DirectHandle<JSReceiver> receiver, bool get_entries, Handle<FixedArray>* result) { DirectHandle<Map> map(Cast<JSReceiver>(*receiver)->map(), isolate); if (!IsJSObjectMap(*map)) return Just(false); if (!map->OnlyHasSimpleProperties()) return Just(false); DirectHandle<JSObject> object(Cast<JSObject>(*receiver), isolate); DirectHandle<DescriptorArray> descriptors(map->instance_descriptors(isolate), 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)); uint32_t 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.SetValue(map->instance_descriptors(isolate)); } for (InternalIndex index : InternalIndex::Range(number_of_own_descriptors)) { HandleScope inner_scope(isolate); DirectHandle<Name> next_key(descriptors->GetKey(index), isolate); if (!IsString(*next_key)) continue; DirectHandle<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(isolate)); PropertyDetails details = descriptors->GetDetails(index); if (!details.IsEnumerable()) continue; if (details.kind() == PropertyKind::kData) { if (details.location() == PropertyLocation::kDescriptor) { prop_value = direct_handle(descriptors->GetStrongValue(index), isolate); } else { Representation representation = details.representation(); FieldIndex field_index = FieldIndex::ForPropertyIndex( *map, details.field_index(), representation); prop_value = JSObject::FastPropertyAt(isolate, 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(isolate, prop_value, Object::GetProperty(&it)); stable = object->map() == *map; descriptors.SetValue(map->instance_descriptors(isolate)); } } 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(isolate, prop_value, Object::GetProperty(&it)); } 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::RightTrimOrEmpty(isolate, values_or_entries, count); return Just(true); } MaybeDirectHandle<FixedArray> GetOwnValuesOrEntries( Isolate* isolate, DirectHandle<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 MaybeDirectHandle<FixedArray>(); if (fast_values_or_entries.FromJust()) return values_or_entries; } PropertyFilter key_filter = static_cast<PropertyFilter>(filter & ~ONLY_ENUMERABLE); DirectHandle<FixedArray> keys; ASSIGN_RETURN_ON_EXCEPTION( isolate, keys, KeyAccumulator::GetKeys(isolate, object, KeyCollectionMode::kOwnOnly, key_filter, GetKeysConversion::kConvertToString)); values_or_entries = isolate->factory()->NewFixedArray(keys->length()); int length = 0; for (int i = 0; i < keys->length(); ++i) { DirectHandle<Name> key(Cast<Name>(keys->get(i)), isolate); if (filter & ONLY_ENUMERABLE) { PropertyDescriptor descriptor; Maybe<bool> did_get_descriptor = JSReceiver::GetOwnPropertyDescriptor( isolate, object, key, &descriptor); MAYBE_RETURN(did_get_descriptor, MaybeDirectHandle<FixedArray>()); if (!did_get_descriptor.FromJust() || !descriptor.enumerable()) continue; } DirectHandle<Object> value; ASSIGN_RETURN_ON_EXCEPTION( isolate, value, Object::GetPropertyOrElement(isolate, object, key)); if (get_entries) { DirectHandle<FixedArray> entry_storage = isolate->factory()->NewFixedArray(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::RightTrimOrEmpty(isolate, values_or_entries, length); } MaybeDirectHandle<FixedArray> JSReceiver::GetOwnValues( Isolate* isolate, DirectHandle<JSReceiver> object, PropertyFilter filter, bool try_fast_path) { return GetOwnValuesOrEntries(isolate, object, filter, try_fast_path, false); } MaybeDirectHandle<FixedArray> JSReceiver::GetOwnEntries( Isolate* isolate, DirectHandle<JSReceiver> object, PropertyFilter filter, bool try_fast_path) { return GetOwnValuesOrEntries(isolate, object, filter, try_fast_path, true); } Maybe<bool> JSReceiver::SetPrototype(Isolate* isolate, DirectHandle<JSReceiver> object, DirectHandle<Object> value, bool from_javascript, ShouldThrow should_throw) { if (IsWasmObject(*object)) { RETURN_FAILURE(isolate, should_throw, NewTypeError(MessageTemplate::kWasmObjectsAreOpaque)); } if (IsJSProxy(*object)) { return JSProxy::SetPrototype(isolate, Cast<JSProxy>(object), value, from_javascript, should_throw); } return JSObject::SetPrototype(isolate, Cast<JSObject>(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 (IsJSProxy(iter.GetCurrent())) return true; } return false; } bool JSReceiver::IsCodeLike(Isolate* isolate) const { DisallowGarbageCollection no_gc; Tagged<Object> maybe_constructor = map()->GetConstructor(); if (!IsJSFunction(maybe_constructor)) return false; if (!Cast<JSFunction>(maybe_constructor)->shared()->IsApiFunction()) { return false; } Tagged<Object> instance_template = Cast<JSFunction>(maybe_constructor) ->shared() ->api_func_data() ->GetInstanceTemplate(); if (IsUndefined(instance_template, isolate)) return false; return Cast<ObjectTemplateInfo>(instance_template)->code_like(); } // static MaybeHandle<JSObject> JSObject::New(DirectHandle<JSFunction> constructor, DirectHandle<JSReceiver> new_target, DirectHandle<AllocationSite> site, NewJSObjectType new_js_object_type) { // 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 = Isolate::Current(); DCHECK(IsConstructor(*constructor)); DCHECK(IsConstructor(*new_target)); DCHECK(!constructor->has_initial_map() || !InstanceTypeChecker::IsJSFunction( constructor->initial_map()->instance_type())); DirectHandle<Map> initial_map; ASSIGN_RETURN_ON_EXCEPTION( isolate, initial_map, JSFunction::GetDerivedMap(isolate, constructor, new_target)); constexpr int initial_capacity = PropertyDictionary::kInitialCapacity; Handle<JSObject> result = isolate->factory()->NewFastOrSlowJSObjectFromMap( initial_map, initial_capacity, AllocationType::kYoung, site, new_js_object_type); return result; } // static MaybeDirectHandle<JSObject> JSObject::NewWithMap( Isolate* isolate, DirectHandle<Map> initial_map, DirectHandle<AllocationSite> site, NewJSObjectType new_js_object_type) { constexpr int initial_capacity = PropertyDictionary::kInitialCapacity; DirectHandle<JSObject> result = isolate->factory()->NewFastOrSlowJSObjectFromMap( initial_map, initial_capacity, AllocationType::kYoung, site, new_js_object_type); return result; } // 9.1.12 ObjectCreate ( proto [ , internalSlotsList ] ) // Notice: This is NOT 19.1.2.2 Object.create ( O, Properties ) MaybeDirectHandle<JSObject> JSObject::ObjectCreate( Isolate* isolate, DirectHandle<JSPrototype> 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. DirectHandle<Map> map = Map::GetObjectCreateMap(isolate, prototype); // Actually allocate the object. return isolate->factory()->NewFastOrSlowJSObjectFromMap(map); } bool JSArray::HasReadOnlyLengthSlowPath(DirectHandle<JSArray> array) { // Look at the object. Isolate* isolate = Isolate::Current(); LookupIterator it(isolate, array, isolate->factory()->length_string(), array, LookupIterator::OWN_SKIP_INTERCEPTOR); CHECK_EQ(LookupIterator::ACCESSOR, it.state()); return it.IsReadOnly(); } void JSObject::MakeElementsWritable(Isolate* isolate, DirectHandle<JSObject> object) { Tagged<FixedArray> raw_elems = Cast<FixedArray>(object->elements()); DirectHandle<FixedArray> elems(raw_elems, isolate); DirectHandle<FixedArray> writable_elems = isolate->factory()->CopyFixedArrayWithMap( elems, isolate->factory()->fixed_array_map()); object->set_elements(*writable_elems); } // For FATAL in JSObject::GetHeaderSize static const char* NonAPIInstanceTypeToString(InstanceType instance_type) { DCHECK(!InstanceTypeChecker::IsJSApiObject(instance_type)); switch (instance_type) { #define WRITE_TYPE(TYPE) \ case TYPE: \ return #TYPE; INSTANCE_TYPE_LIST(WRITE_TYPE) #undef WRITE_TYPE } UNREACHABLE(); } // LINT.IfChange(get_header_size) int JSObject::GetHeaderSize(InstanceType type, bool function_has_prototype_slot) { switch (type) { case JS_SPECIAL_API_OBJECT_TYPE: case JS_API_OBJECT_TYPE: return JSAPIObjectWithEmbedderSlots::BodyDescriptor::kHeaderSize; case JS_ITERATOR_PROTOTYPE_TYPE: case JS_MAP_ITERATOR_PROTOTYPE_TYPE: case JS_OBJECT_PROTOTYPE_TYPE: case JS_OBJECT_TYPE: case JS_PROMISE_PROTOTYPE_TYPE: case JS_REG_EXP_PROTOTYPE_TYPE: case JS_SET_ITERATOR_PROTOTYPE_TYPE: case JS_SET_PROTOTYPE_TYPE: case JS_STRING_ITERATOR_PROTOTYPE_TYPE: case JS_ARRAY_ITERATOR_PROTOTYPE_TYPE: case JS_TYPED_ARRAY_PROTOTYPE_TYPE: case JS_CONTEXT_EXTENSION_OBJECT_TYPE: case JS_ARGUMENTS_OBJECT_TYPE: case JS_ERROR_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: case JS_CLASS_CONSTRUCTOR_TYPE: case JS_PROMISE_CONSTRUCTOR_TYPE: case JS_REG_EXP_CONSTRUCTOR_TYPE: case JS_ARRAY_CONSTRUCTOR_TYPE: #define TYPED_ARRAY_CONSTRUCTORS_SWITCH(Type, type, TYPE, Ctype) \ case TYPE##_TYPED_ARRAY_CONSTRUCTOR_TYPE: TYPED_ARRAYS(TYPED_ARRAY_CONSTRUCTORS_SWITCH) #undef TYPED_ARRAY_CONSTRUCTORS_SWITCH return JSFunction::GetHeaderSize(function_has_prototype_slot); case JS_PRIMITIVE_WRAPPER_TYPE: return JSPrimitiveWrapper::kHeaderSize; case JS_DATE_TYPE: return JSDate::kHeaderSize; case JS_DISPOSABLE_STACK_BASE_TYPE: return JSDisposableStackBase::kHeaderSize; case JS_ASYNC_DISPOSABLE_STACK_TYPE: return JSAsyncDisposableStack::kHeaderSize; case JS_SYNC_DISPOSABLE_STACK_TYPE: return JSSyncDisposableStack::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_DETACHED_TYPED_ARRAY_TYPE: return JSDetachedTypedArray::kHeaderSize; case JS_DATA_VIEW_TYPE: return JSDataView::kHeaderSize; case JS_RAB_GSAB_DATA_VIEW_TYPE: return JSRabGsabDataView::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_MESSAGE_OBJECT_TYPE: return JSMessageObject::kHeaderSize; case JS_EXTERNAL_OBJECT_TYPE: return JSExternalObject::kHeaderSize; case JS_SHADOW_REALM_TYPE: return JSShadowRealm::kHeaderSize; case JS_STRING_ITERATOR_TYPE: return JSStringIterator::kHeaderSize; case JS_ITERATOR_MAP_HELPER_TYPE: return JSIteratorMapHelper::kHeaderSize; case JS_ITERATOR_FILTER_HELPER_TYPE: return JSIteratorFilterHelper::kHeaderSize; case JS_ITERATOR_TAKE_HELPER_TYPE: return JSIteratorTakeHelper::kHeaderSize; case JS_ITERATOR_DROP_HELPER_TYPE: return JSIteratorDropHelper::kHeaderSize; case JS_ITERATOR_FLAT_MAP_HELPER_TYPE: return JSIteratorFlatMapHelper::kHeaderSize; case JS_ITERATOR_CONCAT_HELPER_TYPE: return JSIteratorConcatHelper::kHeaderSize; case JS_MODULE_NAMESPACE_TYPE: return JSModuleNamespace::kHeaderSize; case JS_DEFERRED_MODULE_NAMESPACE_TYPE: return JSDeferredModuleNamespace::kHeaderSize; case JS_SHARED_ARRAY_TYPE: return JSSharedArray::kHeaderSize; case JS_SHARED_STRUCT_TYPE: return JSSharedStruct::kHeaderSize; case JS_ATOMICS_MUTEX_TYPE: return JSAtomicsMutex::kHeaderSize; case JS_ATOMICS_CONDITION_TYPE: return JSAtomicsCondition::kHeaderSize; #ifdef V8_TEMPORAL_SUPPORT case JS_TEMPORAL_DURATION_TYPE: return JSTemporalDuration::kHeaderSize; case JS_TEMPORAL_INSTANT_TYPE: return JSTemporalInstant::kHeaderSize; case JS_TEMPORAL_PLAIN_DATE_TYPE: return JSTemporalPlainDate::kHeaderSize; case JS_TEMPORAL_PLAIN_DATE_TIME_TYPE: return JSTemporalPlainDateTime::kHeaderSize; case JS_TEMPORAL_PLAIN_MONTH_DAY_TYPE: return JSTemporalPlainMonthDay::kHeaderSize; case JS_TEMPORAL_PLAIN_TIME_TYPE: return JSTemporalPlainTime::kHeaderSize; case JS_TEMPORAL_PLAIN_YEAR_MONTH_TYPE: return JSTemporalPlainYearMonth::kHeaderSize; case JS_TEMPORAL_ZONED_DATE_TIME_TYPE: return JSTemporalZonedDateTime::kHeaderSize; #endif // V8_TEMPORAL_SUPPORT case JS_VALID_ITERATOR_WRAPPER_TYPE: return JSValidIteratorWrapper::kHeaderSize; case JS_WRAPPED_FUNCTION_TYPE: return JSWrappedFunction::kHeaderSize; case JS_RAW_JSON_TYPE: return JSRawJson::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_DURATION_FORMAT_TYPE: return JSDurationFormat::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 #if V8_ENABLE_WEBASSEMBLY 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_MEMORY_MAP_DESCRIPTOR_TYPE: return WasmMemoryMapDescriptor::kHeaderSize; case WASM_MODULE_OBJECT_TYPE: return WasmModuleObject::kHeaderSize; case WASM_TABLE_OBJECT_TYPE: return WasmTableObject::kHeaderSize; case WASM_VALUE_OBJECT_TYPE: return WasmValueObject::kHeaderSize; case WASM_TAG_OBJECT_TYPE: return WasmTagObject::kHeaderSize; case WASM_EXCEPTION_PACKAGE_TYPE: return WasmExceptionPackage::kHeaderSize; case WASM_SUSPENDING_OBJECT_TYPE: return WasmSuspendingObject::kHeaderSize; #endif // V8_ENABLE_WEBASSEMBLY default: { // Special type check for API Objects because they are in a large variable // instance type range. if (InstanceTypeChecker::IsJSApiObject(type)) { return JSAPIObjectWithEmbedderSlots::BodyDescriptor::kHeaderSize; } FATAL("unexpected instance type: %s\n", NonAPIInstanceTypeToString(type)); } } } // LINT.ThenChange() MaybeHandle<JSAny> JSObject::GetPropertyWithFailedAccessCheck( LookupIterator* it) { Isolate* isolate = it->isolate(); DirectHandle<JSObject> checked = it->GetHolder<JSObject>(); DirectHandle<InterceptorInfo> interceptor = it->GetInterceptorForFailedAccessCheck(); if (!interceptor.is_null()) { Handle<JSAny> result; bool done; ASSIGN_RETURN_ON_EXCEPTION( isolate, result, GetPropertyWithInterceptorInternal(it, interceptor, &done)); if (done) return result; } // Cross-Origin [[Get]] of Well-Known Symbols does not throw, and returns // undefined. DirectHandle<Name> name = it->GetName(); if (IsSymbol(*name) && Cast<Symbol>(*name)->is_well_known_symbol()) { return it->factory()->undefined_value(); } RETURN_ON_EXCEPTION(isolate, isolate->ReportFailedAccessCheck(checked)); UNREACHABLE(); } Maybe<PropertyAttributes> JSObject::GetPropertyAttributesWithFailedAccessCheck( LookupIterator* it) { Isolate* isolate = it->isolate(); DirectHandle<JSObject> checked = it->GetHolder<JSObject>(); DirectHandle<InterceptorInfo> interceptor = it->GetInterceptorForFailedAccessCheck(); if (!interceptor.is_null()) { Maybe<PropertyAttributes> result = GetPropertyAttributesWithInterceptorInternal(it, interceptor); if (isolate->has_exception()) return Nothing<PropertyAttributes>(); if (result.FromMaybe(ABSENT) != ABSENT) return result; } RETURN_ON_EXCEPTION_VALUE(isolate, isolate->ReportFailedAccessCheck(checked), Nothing<PropertyAttributes>()); UNREACHABLE(); } Maybe<bool> JSObject::SetPropertyWithFailedAccessCheck( LookupIterator* it, DirectHandle<Object> value, Maybe<ShouldThrow> should_throw) { Isolate* isolate = it->isolate(); DirectHandle<JSObject> checked = it->GetHolder<JSObject>(); DirectHandle<InterceptorInfo> interceptor = it->GetInterceptorForFailedAccessCheck(); if (!interceptor.is_null()) { InterceptorResult result; if (!SetPropertyWithInterceptorInternal(it, interceptor, should_throw, value) .To(&result)) { // An exception was thrown in the interceptor. Propagate. return Nothing<bool>(); } switch (result) { case InterceptorResult::kFalse: RETURN_FAILURE(isolate, GetShouldThrow(isolate, should_throw), NewTypeError(MessageTemplate::kStrictCannotSetProperty, it->GetName(), it->GetReceiver())); case InterceptorResult::kTrue: return Just(true); case InterceptorResult::kNotIntercepted: // Fall through to report failed access check. break; } } RETURN_ON_EXCEPTION_VALUE(isolate, isolate->ReportFailedAccessCheck(checked), Nothing<bool>()); UNREACHABLE(); } void JSObject::SetNormalizedProperty(DirectHandle<JSObject> object, DirectHandle<Name> name, DirectHandle<Object> value, PropertyDetails details) { DCHECK(!object->HasFastProperties()); DCHECK(IsUniqueName(*name)); Isolate* isolate = Isolate::Current(); uint32_t hash = name->hash(); if (IsJSGlobalObject(*object)) { auto global_obj = Cast<JSGlobalObject>(object); DirectHandle<GlobalDictionary> dictionary( global_obj->global_dictionary(kAcquireLoad), 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(), !global_obj->map()->IsPrototypeValidityCellValid()); auto cell_type = IsUndefined(*value, roots) ? PropertyCellType::kUndefined : PropertyCellType::kConstant; details = details.set_cell_type(cell_type); auto cell = isolate->factory()->NewPropertyCell(name, details, value); dictionary = GlobalDictionary::Add(isolate, dictionary, name, cell, details) .ToHandleChecked(); global_obj->set_global_dictionary(*dictionary, kReleaseStore); } else { PropertyCell::PrepareForAndSetValue(isolate, dictionary, entry, value, details); DCHECK_EQ(dictionary->CellAt(entry)->value(), *value); } } else { if constexpr (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) { DirectHandle<SwissNameDictionary> dictionary( object->property_dictionary_swiss(), isolate); InternalIndex entry = dictionary->FindEntry(isolate, *name); if (entry.is_not_found()) { DCHECK_IMPLIES(object->map()->is_prototype_map(), !object->map()->IsPrototypeValidityCellValid()); dictionary = SwissNameDictionary::Add(isolate, dictionary, name, value, details); object->SetProperties(*dictionary); } else { dictionary->ValueAtPut(entry, *value); dictionary->DetailsAtPut(entry, details); } } else { DirectHandle<NameDictionary> dictionary(object->property_dictionary(), isolate); InternalIndex entry = dictionary->FindEntry(isolate, name); if (entry.is_not_found()) { DCHECK_IMPLIES(object->map()->is_prototype_map(), !object->map()->IsPrototypeValidityCellValid()); dictionary = NameDictionary::Add(isolate, dictionary, name, value, details) .ToHandleChecked(); 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); } // TODO(pthier): Add flags to swiss dictionaries. if (name->IsInteresting(isolate)) { dictionary->set_may_have_interesting_properties(true); } } } } void JSObject::SetNormalizedElement(DirectHandle<JSObject> object, uint32_t index, DirectHandle<Object> value, PropertyDetails details) { DCHECK_EQ(object->GetElementsKind(), DICTIONARY_ELEMENTS); Isolate* isolate = Isolate::Current(); DirectHandle<NumberDictionary> dictionary( Cast<NumberDictionary>(object->elements()), isolate); dictionary = NumberDictionary::Set(isolate, dictionary, index, value, object, details); object->set_elements(*dictionary); } void JSObject::JSObjectShortPrint(StringStream* accumulator) { switch (map()->instance_type()) { case JS_ARRAY_TYPE: { double length = IsUndefined(Cast<JSArray>(*this)->length()) ? 0 : Object::NumberValue(Cast<JSArray>(*this)->length()); accumulator->Add("<JSArray[%u]>", static_cast<uint32_t>(length)); break; } case JS_BOUND_FUNCTION_TYPE: { Tagged<JSBoundFunction> bound_function = Cast<JSBoundFunction>(*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"); Tagged<JSRegExp> regexp = Cast<JSRegExp>(*this); if (IsString(regexp->source())) { accumulator->Add(" "); Cast<String>(regexp->source())->StringShortPrint(accumulator); } accumulator->Add(">"); break; } case JS_PROMISE_CONSTRUCTOR_TYPE: case JS_REG_EXP_CONSTRUCTOR_TYPE: case JS_ARRAY_CONSTRUCTOR_TYPE: #define TYPED_ARRAY_CONSTRUCTORS_SWITCH(Type, type, TYPE, Ctype) \ case TYPE##_TYPED_ARRAY_CONSTRUCTOR_TYPE: TYPED_ARRAYS(TYPED_ARRAY_CONSTRUCTORS_SWITCH) #undef TYPED_ARRAY_CONSTRUCTORS_SWITCH case JS_CLASS_CONSTRUCTOR_TYPE: case JS_FUNCTION_TYPE: { Tagged<JSFunction> function = Cast<JSFunction>(*this); std::unique_ptr<char[]> fun_name = function->shared()->DebugNameCStr(); if (fun_name[0] != '\0') { accumulator->Add("<JSFunction "); accumulator->Add(fun_name.get()); } else { accumulator->Add("<JSFunction"); } if (v8_flags.trace_file_names) { if (Tagged<Script> script; TryCast<Script>(function->shared()->script(), &script)) { Tagged<Object> source_name = script->name(); if (Tagged<String> str; TryCast<String>(source_name, &str)) { 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; } case JS_SHARED_ARRAY_TYPE: accumulator->Add("<JSSharedArray>"); break; case JS_SHARED_STRUCT_TYPE: accumulator->Add("<JSSharedStruct>"); break; case JS_ATOMICS_MUTEX_TYPE: accumulator->Add("<JSAtomicsMutex>"); break; case JS_ATOMICS_CONDITION_TYPE: accumulator->Add("<JSAtomicsCondition>"); break; case JS_MESSAGE_OBJECT_TYPE: accumulator->Add("<JSMessageObject>"); break; case JS_EXTERNAL_OBJECT_TYPE: accumulator->Add("<JSExternalObject>"); break; case CPP_HEAP_EXTERNAL_OBJECT_TYPE: accumulator->Add("<CppHeapExternalObject>"); break; default: { Tagged<Map> map_of_this = map(); Tagged<Object> constructor = map_of_this->GetConstructor(); bool printed = false; bool is_global_proxy = IsJSGlobalProxy(*this); if (IsJSFunction(constructor)) { Tagged<SharedFunctionInfo> sfi = Cast<JSFunction>(constructor)->shared(); Tagged<String> constructor_name = sfi->Name(); if (constructor_name->length() > 0) { accumulator->Add(is_global_proxy ? "<GlobalObject " : "<"); accumulator->Put(constructor_name); accumulator->Add(" %smap = %p", map_of_this->is_deprecated() ? "deprecated-" : "", map_of_this); printed = true; } } else if (IsFunctionTemplateInfo(constructor)) { accumulator->Add("<RemoteObject>"); printed = true; } if (!printed) { accumulator->Add("<JS"); if (is_global_proxy) { accumulator->Add("GlobalProxy"); } else if (IsJSGlobalObject(*this)) { accumulator->Add("GlobalObject"); } else { accumulator->Add("Object"); } } if (IsJSPrimitiveWrapper(*this)) { accumulator->Add(" value = "); ShortPrint(Cast<JSPrimitiveWrapper>(*this)->value(), accumulator); } accumulator->Put('>'); break; } } } void JSObject::PrintElementsTransition( FILE* file, DirectHandle<JSObject> object, ElementsKind from_kind, DirectHandle<FixedArrayBase> from_elements, ElementsKind to_kind, DirectHandle<FixedArrayBase> to_elements) { if (from_kind != to_kind) { OFStream os(file); os << "elements transition [" << ElementsKindToString(from_kind) << " -> " << ElementsKindToString(to_kind) << "] in "; JavaScriptFrame::PrintTop(Isolate::Current(), file, false, true); PrintF(file, " for "); ShortPrint(*object, file); PrintF(file, " from "); ShortPrint(*from_elements, file); PrintF(file, " to "); ShortPrint(*to_elements, file); PrintF(file, "\n"); } } void JSObject::PrintInstanceMigration(FILE* file, Tagged<Map> original_map, Tagged<Map> new_map) { if (new_map->is_dictionary_map()) { PrintF(file, "[migrating to slow]\n"); return; } PrintF(file, "[migrating]"); Isolate* isolate = Isolate::Current(); Tagged<DescriptorArray> o = original_map->instance_descriptors(isolate); Tagged<DescriptorArray> n = new_map->instance_descriptors(isolate); for (InternalIndex i : original_map->IterateOwnDescriptors()) { Representation o_r = o->GetDetails(i).representation(); Representation n_r = n->GetDetails(i).representation(); Tagged<Name> name = o->GetKey(i); if (IsString(name)) { Cast<String>(name)->PrintOn(file); } else { PrintF(file, "{symbol %p}", reinterpret_cast<void*>(name.ptr())); } if (!o_r.Equals(n_r)) { PrintF(file, ":%s->%s ", o_r.Mnemonic(), n_r.Mnemonic()); } else if (o->GetDetails(i).location() == PropertyLocation::kDescriptor && n->GetDetails(i).location() == PropertyLocation::kField) { 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"); } // static bool JSObject::IsUnmodifiedApiObject(FullObjectSlot o) { Tagged<Object> object = *o; if (IsSmi(object)) return false; Tagged<HeapObject> heap_object = Cast<HeapObject>(object); Tagged<Map> map = heap_object->map(); if (!InstanceTypeChecker::IsJSObject(map)) return false; if (!JSObject::IsDroppableApiObject(map)) return false; Tagged<Object> maybe_constructor = map->GetConstructor(); if (!IsJSFunction(maybe_constructor)) return false; Tagged<JSObject> js_object = Cast<JSObject>(object); if (js_object->elements()->length() != 0) return false; // Check that the object is not a key in a WeakMap (over-approximation). if (!IsUndefined(js_object->GetIdentityHash())) return false; Tagged<JSFunction> constructor = Cast<JSFunction>(maybe_constructor); return constructor->initial_map() == map; } // static void JSObject::UpdatePrototypeUserRegistration(DirectHandle<Map> old_map, DirectHandle<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(), kReleaseStore); old_map->set_prototype_info(Smi::zero(), kReleaseStore); if (v8_flags.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->has_prototype_info()) { // The new map isn't registered with its prototype yet; reflect this fact // in the PrototypeInfo it just inherited from the old map. Cast<PrototypeInfo>(new_map->prototype_info()) ->set_registry_slot(PrototypeInfo::UNREGISTERED); } JSObject::LazyRegisterPrototypeUser(new_map, isolate); } } // static void JSObject::NotifyMapChange(DirectHandle<Map> old_map, DirectHandle<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, DirectHandle<JSObject> object, DirectHandle<Map> new_map) { DirectHandle<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->set_map(isolate, *new_map, kReleaseStore); return; } // If the map adds a new kDescriptor property, simply set the map. PropertyDetails details = new_map->GetLastDescriptorDetails(isolate); if (details.location() == PropertyLocation::kDescriptor) { object->set_map(isolate, *new_map, kReleaseStore); 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::ForDetails(*new_map, details); if (index.is_inobject() || index.outobject_array_index() < object->property_array(isolate)->length()) { // Allocate HeapNumbers for double fields. if (index.is_double()) { auto value = isolate->factory()->NewHeapNumberWithHoleNaN(); object->FastPropertyAtPut(index, *value); } object->set_map(isolate, *new_map, kReleaseStore); 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; DirectHandle<PropertyArray> old_storage(object->property_array(isolate), isolate); DirectHandle<PropertyArray> new_storage = isolate->factory()->CopyPropertyArrayAndGrow(old_storage, grow_by); // Properly initialize newly added property. DirectHandle<Object> value; if (details.representation().IsDouble()) { value = isolate->factory()->NewHeapNumberWithHoleNaN(); } else { value = isolate->factory()->uninitialized_value(); } DCHECK_EQ(PropertyLocation::kField, details.location()); DCHECK_EQ(PropertyKind::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. DisallowGarbageCollection no_gc; // Set the new property value and do the map transition. object->SetProperties(*new_storage); object->set_map(isolate, *new_map, kReleaseStore); return; } int old_number_of_fields; int number_of_fields = new_map->NumberOfFields(ConcurrencyMode::kSynchronous); 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, ConcurrencyMode::kSynchronous)) { object->set_map(isolate, *new_map, kReleaseStore); return; } int total_size = number_of_fields + unused; int external = total_size - inobject; DirectHandle<PropertyArray> array = isolate->factory()->NewPropertyArray(external); // We use this array to temporarily store the inobject properties. DirectHandle<FixedArray> inobject_props = isolate->factory()->NewFixedArray(inobject); DirectHandle<DescriptorArray> old_descriptors( old_map->instance_descriptors(isolate), isolate); DirectHandle<DescriptorArray> new_descriptors( new_map->instance_descriptors(isolate), 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() != PropertyLocation::kField) continue; DCHECK_EQ(PropertyKind::kData, details.kind()); PropertyDetails old_details = old_descriptors->GetDetails(i); Representation old_representation = old_details.representation(); Representation representation = details.representation(); Handle<UnionOf<JSAny, Hole>> value; if (old_details.location() == PropertyLocation::kDescriptor) { if (old_details.kind() == PropertyKind::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(PropertyKind::kData, old_details.kind()); value = handle(Cast<JSAny>(old_descriptors->GetStrongValue(isolate, i)), isolate); DCHECK(!old_representation.IsDouble() && !representation.IsDouble()); } } else { DCHECK_EQ(PropertyLocation::kField, old_details.location()); FieldIndex index = FieldIndex::ForDetails(*old_map, old_details); value = handle(object->RawFastPropertyAt(isolate, index), isolate); if (!old_representation.IsDouble() && representation.IsDouble()) { DCHECK_IMPLIES(old_representation.IsNone(), IsUninitializedHole(*value, isolate)); value = Object::NewStorageFor(isolate, value, representation); } else if (old_representation.IsDouble() && !representation.IsDouble()) { value = Object::WrapForRead(isolate, Cast<JSAny>(value), old_representation); } } DCHECK(!(representation.IsDouble() && IsSmi(*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); } } for (InternalIndex i : InternalIndex::Range(old_nof, new_nof)) { PropertyDetails details = new_descriptors->GetDetails(i); if (details.location() != PropertyLocation::kField) continue; DCHECK_EQ(PropertyKind::kData, details.kind()); DirectHandle<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. DisallowGarbageCollection no_gc; Heap* heap = isolate->heap(); // 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); Tagged<Object> value = inobject_props->get(i); object->FastPropertyAtPut(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) { heap->NotifyObjectSizeChange(*object, old_instance_size, new_instance_size, 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->set_map(isolate, *new_map, kReleaseStore); } void MigrateFastToSlow(Isolate* isolate, DirectHandle<JSObject> object, DirectHandle<Map> new_map, int expected_additional_properties) { // The global object is always normalized. DCHECK(!IsJSGlobalObject(*object, isolate)); // JSGlobalProxy must never be normalized DCHECK(!IsJSGlobalProxy(*object, isolate)); DCHECK_IMPLIES(new_map->is_prototype_map(), !new_map->IsPrototypeValidityCellValid()); HandleScope scope(isolate); DirectHandle<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. constexpr int initial_capacity = PropertyDictionary::kInitialCapacity; property_count += initial_capacity; } DirectHandle<NameDictionary> dictionary; DirectHandle<SwissNameDictionary> ord_dictionary; if constexpr (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) { ord_dictionary = isolate->factory()->NewSwissNameDictionary(property_count); } else { dictionary = isolate->factory()->NewNameDictionary(property_count); } DirectHandle<DescriptorArray> descs(map->instance_descriptors(isolate), isolate); for (InternalIndex i : InternalIndex::Range(real_size)) { PropertyDetails details = descs->GetDetails(i); DirectHandle<Name> key(descs->GetKey(isolate, i), isolate); DirectHandle<Object> value; if (details.location() == PropertyLocation::kField) { FieldIndex index = FieldIndex::ForDetails(*map, details); if (details.kind() == PropertyKind::kData) { value = direct_handle(object->RawFastPropertyAt(isolate, index), isolate); if (details.representation().IsDouble()) { DCHECK(IsHeapNumber(*value, isolate)); double old_value = Cast<HeapNumber>(value)->value(); value = isolate->factory()->NewHeapNumber(old_value); } } else { DCHECK_EQ(PropertyKind::kAccessor, details.kind()); value = direct_handle(object->RawFastPropertyAt(isolate, index), isolate); } } else { DCHECK_EQ(PropertyLocation::kDescriptor, details.location()); value = direct_handle(descs->GetStrongValue(isolate, i), isolate); } DCHECK(!value.is_null()); PropertyConstness constness = V8_DICT_PROPERTY_CONST_TRACKING_BOOL ? details.constness() : PropertyConstness::kMutable; PropertyDetails d(details.kind(), details.attributes(), constness); if constexpr (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) { ord_dictionary = SwissNameDictionary::Add(isolate, ord_dictionary, key, value, d); } else { dictionary = NameDictionary::Add(isolate, dictionary, key, value, d) .ToHandleChecked(); } } if constexpr (!V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) { // Copy the next enumeration index from instance descriptor. dictionary->set_next_enumeration_index(real_size + 1); // TODO(pthier): Add flags to swiss dictionaries. dictionary->set_may_have_interesting_properties( map->may_have_interesting_properties()); } // From here on we cannot fail and we shouldn't GC anymore. DisallowGarbageCollection no_gc; Heap* heap = isolate->heap(); // 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->NotifyObjectSizeChange(*object, old_instance_size, new_instance_size, 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->set_map(isolate, *new_map, kReleaseStore); if constexpr (V8_ENABLE_SWISS_NAME_DICTIONARY_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) { for (int i = 0; i < inobject_properties; i++) { FieldIndex index = FieldIndex::ForPropertyIndex(*new_map, i); object->FastPropertyAtPut(index, Smi::zero()); } } #ifdef DEBUG if (v8_flags.trace_normalization) { StdoutStream os; os << "Object properties have been normalized:\n"; Print(*object, os); } #endif } } // namespace void JSObject::MigrateToMap(Isolate* isolate, DirectHandle<JSObject> object, DirectHandle<Map> new_map, int expected_additional_properties) { if (object->map(isolate) == *new_map) return; DirectHandle<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->set_map(isolate, *new_map, kReleaseStore); } 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(IsUndefined(new_map->GetBackPointer(isolate), 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 DisallowGarbageCollection too. } void JSObject::ForceSetPrototype(Isolate* isolate, DirectHandle<JSObject> object, DirectHandle<JSPrototype> proto) { // object.__proto__ = proto; DirectHandle<Map> old_map(object->map(), isolate); DirectHandle<Map> new_map = Map::Copy(isolate, old_map, "ForceSetPrototype"); Map::SetPrototype(isolate, new_map, proto); JSObject::MigrateToMap(isolate, object, new_map); } Maybe<InterceptorResult> JSObject::SetPropertyWithInterceptor( LookupIterator* it, Maybe<ShouldThrow> should_throw, DirectHandle<Object> value) { DCHECK_EQ(LookupIterator::INTERCEPTOR, it->state()); return SetPropertyWithInterceptorInternal(it, it->GetInterceptor(), should_throw, value); } DirectHandle<Map> JSObject::GetElementsTransitionMap( Isolate* isolate, DirectHandle<JSObject> object, ElementsKind to_kind) { DirectHandle<Map> map(object->map(), isolate); return Map::TransitionElementsTo(isolate, map, to_kind); } void JSObject::AllocateStorageForMap(Isolate* isolate, DirectHandle<JSObject> object, DirectHandle<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(isolate, object); } else { TransitionElementsKind(isolate, object, to_kind); } map = MapUpdater{isolate, map}.ReconfigureElementsKind(to_kind); } int number_of_fields = map->NumberOfFields(ConcurrencyMode::kSynchronous); 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. DirectHandle<DescriptorArray> descriptors(map->instance_descriptors(isolate), isolate); DirectHandle<FixedArray> storage = isolate->factory()->NewFixedArray(inobject); DirectHandle<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::ForDetails(*map, details); 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); for (int i = 0; i < inobject; i++) { FieldIndex index = FieldIndex::ForPropertyIndex(*map, i); Tagged<Object> value = storage->get(i); object->FastPropertyAtPut(index, value); } object->set_map(isolate, *map, kReleaseStore); } void JSObject::MigrateInstance(Isolate* isolate, DirectHandle<JSObject> object) { Handle<Map> original_map(object->map(), isolate); DirectHandle<Map> map = Map::Update(isolate, original_map); map->set_is_migration_target(true); JSObject::MigrateToMap(isolate, object, map); if (v8_flags.trace_migration) { object->PrintInstanceMigration(stdout, *original_map, *map); } #if VERIFY_HEAP if (v8_flags.verify_heap) { object->JSObjectVerify(isolate); } #endif } // static bool JSObject::TryMigrateInstance(Isolate* isolate, DirectHandle<JSObject> object) { DisallowDeoptimization no_deoptimization(isolate); Handle<Map> original_map(object->map(), isolate); DirectHandle<Map> new_map; if (!Map::TryUpdate(isolate, original_map).ToHandle(&new_map)) { return false; } JSObject::MigrateToMap(isolate, object, new_map); if (v8_flags.trace_migration && *original_map != object->map()) { object->PrintInstanceMigration(stdout, *original_map, object->map()); } #if VERIFY_HEAP if (v8_flags.verify_heap) { object->JSObjectVerify(isolate); } #endif return true; } namespace { bool TryFastAddDataProperty(Isolate* isolate, DirectHandle<JSObject> object, DirectHandle<Name> name, DirectHandle<Object> value, PropertyAttributes attributes) { DCHECK(IsUniqueName(*name)); Tagged<Map> map = TransitionsAccessor(isolate, object->map()) .SearchTransition(*name, PropertyKind::kData, attributes); if (map.is_null()) return false; DCHECK(!map->is_dictionary_map()); DirectHandle<Map> new_map(map, isolate); if (map->is_deprecated()) { new_map = Map::Update(isolate, new_map); if (new_map->is_dictionary_map()) return false; } InternalIndex descriptor = new_map->LastAdded(); new_map = Map::PrepareForDataProperty(isolate, new_map, descriptor, PropertyConstness::kConst, value); JSObject::MigrateToMap(isolate, object, new_map); // TODO(leszeks): Avoid re-loading the property details, which we already // loaded in PrepareForDataProperty. object->WriteToField(descriptor, new_map->instance_descriptors()->GetDetails(descriptor), *value); return true; } } // namespace void JSObject::AddProperty(Isolate* isolate, DirectHandle<JSObject> object, DirectHandle<Name> name, DirectHandle<Object> value, PropertyAttributes attributes) { name = isolate->factory()->InternalizeName(name); if (TryFastAddDataProperty(isolate, object, name, value, attributes)) { return; } LookupIterator it(isolate, object, name, object, LookupIterator::OWN_SKIP_INTERCEPTOR); CHECK_NE(LookupIterator::ACCESS_CHECK, it.state()); #ifdef DEBUG uint32_t index; DCHECK(!IsJSProxy(*object)); DCHECK(!IsWasmObject(*object)); DCHECK(!name->AsArrayIndex(&index)); Maybe<PropertyAttributes> maybe = GetPropertyAttributes(&it); DCHECK(maybe.IsJust()); DCHECK(!it.IsFound()); DCHECK(object->map()->is_extensible() || name->IsAnyPrivate()); #endif CHECK(Object::AddDataProperty(&it, value, attributes, Just(ShouldThrow::kThrowOnError), StoreOrigin::kNamed) .IsJust()); } void JSObject::AddProperty(Isolate* isolate, DirectHandle<JSObject> object, const char* name, DirectHandle<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. Maybe<bool> JSObject::DefineOwnPropertyIgnoreAttributes( LookupIterator* it, DirectHandle<Object> value, PropertyAttributes attributes, Maybe<ShouldThrow> should_throw, AccessorInfoHandling handling, EnforceDefineSemantics semantics, StoreOrigin store_origin, MaybeDirectHandle<Object> old_value) { it->UpdateProtector(value, old_value); for (;; it->Next()) { switch (it->state()) { case LookupIterator::JSPROXY: case LookupIterator::TRANSITION: case LookupIterator::STRING_LOOKUP_START_OBJECT: case LookupIterator::MODULE_NAMESPACE: UNREACHABLE(); case LookupIterator::WASM_OBJECT: continue; // {AddDataProperty} will throw if no other case is hit. case LookupIterator::ACCESS_CHECK: if (!it->HasAccess()) { Isolate* isolate = it->isolate(); RETURN_ON_EXCEPTION_VALUE( isolate, isolate->ReportFailedAccessCheck(it->GetHolder<JSObject>()), Nothing<bool>()); UNREACHABLE(); } continue; // 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: { InterceptorResult result; if (semantics == EnforceDefineSemantics::kDefine) { PropertyDescriptor descriptor; descriptor.set_configurable((attributes & DONT_DELETE) != 0); descriptor.set_enumerable((attributes & DONT_ENUM) != 0); descriptor.set_writable((attributes & READ_ONLY) != 0); descriptor.set_value(Cast<JSAny>(value)); if (!DefinePropertyWithInterceptorInternal(it, it->GetInterceptor(), should_throw, &descriptor) .To(&result)) { // An exception was thrown in the interceptor. Propagate. return Nothing<bool>(); } } else { DCHECK_EQ(semantics, EnforceDefineSemantics::kSet); if (handling == DONT_FORCE_FIELD) { if (!JSObject::SetPropertyWithInterceptor(it, should_throw, value) .To(&result)) { // An exception was thrown in the interceptor. Propagate. return Nothing<bool>(); } } else { result = InterceptorResult::kNotIntercepted; } } switch (result) { case InterceptorResult::kFalse: { // Throw TypeError if necessary in case the callback failed // to define the property. return Object::CannotCreateProperty( it->isolate(), it->GetReceiver(), it->GetName(), should_throw); } case InterceptorResult::kTrue: return Just(true); case InterceptorResult::kNotIntercepted: // Proceed lookup. break; } if (semantics == EnforceDefineSemantics::kDefine) { it->Restart(); Maybe<bool> can_define = JSObject::CheckIfCanDefineAsConfigurable( it->isolate(), it, value, should_throw); if (can_define.IsNothing() || !can_define.FromJust()) { return can_define; } } // The interceptor declined to handle the operation, so proceed defining // own property without the interceptor. Isolate* isolate = it->isolate(); DirectHandle<JSAny> receiver = it->GetReceiver(); LookupIterator own_lookup(isolate, receiver, it->GetKey(), LookupIterator::OWN_SKIP_INTERCEPTOR); return JSObject::DefineOwnPropertyIgnoreAttributes( &own_lookup, value, attributes, should_throw, handling, semantics, store_origin); } case LookupIterator::ACCESSOR: { DirectHandle<Object> accessors = it->GetAccessors(); // Special handling for AccessorInfo, which behaves like a data // property. if (IsAccessorInfo(*accessors) && 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::TYPED_ARRAY_INDEX_NOT_FOUND: 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); } // The non-matching attribute case for JSTypedArrays has already been // handled by JSTypedArray::DefineOwnProperty. DCHECK(!it->IsElement() || !Cast<JSObject>(it->GetReceiver()) ->HasTypedArrayOrRabGsabTypedArrayElements()); // Reconfigure the data property if the attributes mismatch. it->ReconfigureDataProperty(value, attributes); return Just(true); } case LookupIterator::NOT_FOUND: return Object::AddDataProperty(it, value, attributes, should_throw, store_origin, semantics); } UNREACHABLE(); } } MaybeDirectHandle<Object> JSObject::SetOwnPropertyIgnoreAttributes( DirectHandle<JSObject> object, DirectHandle<Name> name, DirectHandle<Object> value, PropertyAttributes attributes) { DCHECK(!IsTheHole(*value)); LookupIterator it(Isolate::Current(), object, name, object, LookupIterator::OWN); return DefineOwnPropertyIgnoreAttributes(&it, value, attributes); } MaybeDirectHandle<Object> JSObject::SetOwnElementIgnoreAttributes( DirectHandle<JSObject> object, size_t index, DirectHandle<Object> value, PropertyAttributes attributes) { DCHECK(!IsJSTypedArray(*object)); Isolate* isolate = Isolate::Current(); LookupIterator it(isolate, object, index, object, LookupIterator::OWN); return DefineOwnPropertyIgnoreAttributes(&it, value, attributes); } MaybeDirectHandle<Object> JSObject::DefinePropertyOrElementIgnoreAttributes( DirectHandle<JSObject> object, DirectHandle<Name> name, DirectHandle<Object> value, PropertyAttributes attributes) { Isolate* isolate = Isolate::Current(); PropertyKey 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, DirectHandle<JSObject> object, PropertyNormalizationMode mode, int expected_additional_properties, bool use_cache, const char* reason) { if (!object->HasFastProperties()) return; DirectHandle<Map> map(object->map(), isolate); DirectHandle<Map> new_map = Map::Normalize(isolate, map, map->elements_kind(), {}, mode, use_cache, reason); JSObject::MigrateToMap(isolate, object, new_map, expected_additional_properties); } void JSObject::MigrateSlowToFast(DirectHandle<JSObject> object, int unused_property_fields, const char* reason) { if (object->HasFastProperties()) return; DCHECK(!IsJSGlobalObject(*object)); Isolate* isolate = Isolate::Current(); Factory* factory = isolate->factory(); DirectHandle<NameDictionary> dictionary; DirectHandle<SwissNameDictionary> swiss_dictionary; int number_of_elements; if constexpr (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) { swiss_dictionary = direct_handle(object->property_dictionary_swiss(), isolate); number_of_elements = swiss_dictionary->NumberOfElements(); } else { dictionary = direct_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; DirectHandle<FixedArray> iteration_order; int iteration_length; if constexpr (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) { // |iteration_order| remains empty handle, we don't need it. iteration_length = swiss_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 constexpr (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) { InternalIndex index(swiss_dictionary->EntryForEnumerationIndex(i)); Tagged<Object> key = swiss_dictionary->KeyAt(index); if (!SwissNameDictionary::IsKey(roots, key)) { // Ignore deleted entries. continue; } kind = swiss_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 == PropertyKind::kData) { number_of_fields += 1; } } DirectHandle<Map> old_map(object->map(), isolate); int inobject_props = old_map->GetInObjectProperties(); // Allocate new map. DirectHandle<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_properties( 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) { DisallowGarbageCollection no_gc; DCHECK_LE(unused_property_fields, inobject_props); // Transform the object. new_map->SetInObjectUnusedPropertyFields(inobject_props); object->set_map(isolate, *new_map, kReleaseStore); object->SetProperties(ReadOnlyRoots(isolate).empty_fixed_array()); // Check that it really works. DCHECK(object->HasFastProperties()); if (v8_flags.log_maps) { LOG(isolate, MapEvent("SlowToFast", old_map, new_map, reason)); } return; } // Allocate the instance descriptor. DirectHandle<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. DirectHandle<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++) { Tagged<Name> k; Tagged<Object> value; PropertyDetails details = PropertyDetails::Empty(); if constexpr (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) { InternalIndex index(swiss_dictionary->EntryForEnumerationIndex(i)); Tagged<Object> key_obj = swiss_dictionary->KeyAt(index); if (!SwissNameDictionary::IsKey(roots, key_obj)) { continue; } k = Cast<Name>(key_obj); value = swiss_dictionary->ValueAt(index); details = swiss_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(IsUniqueName(k)); DirectHandle<Name> key(k, isolate); // Properly mark the {new_map} if the {key} is an "interesting symbol". if (key->IsInteresting(isolate)) { new_map->set_may_have_interesting_properties(true); } DCHECK_EQ(PropertyLocation::kField, details.location()); DCHECK_IMPLIES(!V8_DICT_PROPERTY_CONST_TRACKING_BOOL, details.constness() == PropertyConstness::kMutable); Descriptor d; if (details.kind() == PropertyKind::kData) { // Ensure that we make constant field only when elements kind is not // transitionable. PropertyConstness constness = is_transitionable_elements_kind ? PropertyConstness::kMutable : PropertyConstness::kConst; // TODO(v8:11248): Consider always setting constness to kMutable // once all prototypes stay in dictionary mode and we are not interested // in tracking constness for fast mode properties anymore. d = Descriptor::DataField( key, current_offset, details.attributes(), constness, // TODO(verwaest): value->OptimalRepresentation(); Representation::Tagged(), MaybeObjectDirectHandle(FieldType::Any(isolate))); } else { DCHECK_EQ(PropertyKind::kAccessor, details.kind()); d = Descriptor::AccessorConstant(key, direct_handle(value, isolate), details.attributes()); } details = d.GetDetails(); if (details.location() == PropertyLocation::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(); DisallowGarbageCollection no_gc; new_map->InitializeDescriptors(isolate, *descriptors); if (number_of_allocated_fields == 0) { new_map->SetInObjectUnusedPropertyFields(unused_property_fields); } else { new_map->SetOutOfObjectUnusedPropertyFields(unused_property_fields); } if (v8_flags.log_maps) { LOG(isolate, MapEvent("SlowToFast", old_map, new_map, reason)); } // Transform the object. object->set_map(isolate, *new_map, kReleaseStore); object->SetProperties(*fields); DCHECK(IsJSObject(*object)); // Check that it really works. DCHECK(object->HasFastProperties()); } void JSObject::RequireSlowElements(Tagged<NumberDictionary> dictionary) { DCHECK_NE(dictionary, ReadOnlyRoots(Isolate::Current()).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()); } } DirectHandle<NumberDictionary> JSObject::NormalizeElements( Isolate* isolate, DirectHandle<JSObject> object) { DCHECK(!object->HasTypedArrayOrRabGsabTypedArrayElements()); bool is_sloppy_arguments = object->HasSloppyArgumentsElements(); { DisallowGarbageCollection no_gc; Tagged<FixedArrayBase> elements = object->elements(); if (is_sloppy_arguments) { elements = Cast<SloppyArgumentsElements>(elements)->arguments(); } if (IsNumberDictionary(elements)) { return direct_handle(Cast<NumberDictionary>(elements), isolate); } } DCHECK(object->HasSmiOrObjectElements() || object->HasDoubleElements() || object->HasFastArgumentsElements() || object->HasFastStringWrapperElements() || object->HasSealedElements() || object->HasNonextensibleElements()); DirectHandle<NumberDictionary> dictionary = object->GetElementsAccessor()->Normalize(isolate, 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; DirectHandle<Map> new_map = JSObject::GetElementsTransitionMap(isolate, object, target_kind); // Set the new map first to satisfy the elements type assert in // set_elements(). JSObject::MigrateToMap(isolate, object, new_map); if (is_sloppy_arguments) { Cast<SloppyArgumentsElements>(object->elements()) ->set_arguments(*dictionary); } else { object->set_elements(*dictionary); } #ifdef DEBUG if (v8_flags.trace_normalization) { StdoutStream os; os << "Object elements have been normalized:\n"; Print(*object, os); } #endif DCHECK(object->HasDictionaryElements() || object->HasSlowArgumentsElements() || object->HasSlowStringWrapperElements()); return dictionary; } Maybe<InterceptorResult> 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()); DirectHandle<InterceptorInfo> interceptor(it->GetInterceptor()); if (!interceptor->has_deleter()) { return Just(InterceptorResult::kNotIntercepted); } DirectHandle<JSObject> holder = it->GetHolder<JSObject>(); PropertyCallbackArguments args(isolate, it->GetHolderForApi(), Just(should_throw)); v8::Intercepted intercepted = it->IsElement(*holder) ? args.CallIndexedDeleter(isolate, interceptor, it->array_index()) : args.CallNamedDeleter(isolate, interceptor, it->name()); return args.GetBooleanReturnValue(isolate, intercepted, "Deleter"); } Maybe<bool> JSObject::CreateDataProperty(Isolate* isolate, DirectHandle<JSObject> object, PropertyKey key, DirectHandle<Object> value, Maybe<ShouldThrow> should_throw) { if (!key.is_element()) { if (TryFastAddDataProperty(isolate, object, key.name(), value, NONE)) { return Just(true); } } LookupIterator it(isolate, object, key, LookupIterator::OWN); Maybe<bool> can_define = JSObject::CheckIfCanDefineAsConfigurable( isolate, &it, value, should_throw); if (can_define.IsNothing() || !can_define.FromJust()) { return can_define; } RETURN_ON_EXCEPTION_VALUE(isolate, DefineOwnPropertyIgnoreAttributes(&it, value, NONE), Nothing<bool>()); return Just(true); } namespace { template <typename Dictionary> bool TestDictionaryPropertiesIntegrityLevel(Tagged<Dictionary> dict, ReadOnlyRoots roots, PropertyAttributes level) { DCHECK(level == SEALED || level == FROZEN); for (InternalIndex i : dict->IterateEntries()) { Tagged<Object> key; if (!dict->ToKey(roots, i, &key)) continue; if (Object::FilterKey(key, ALL_PROPERTIES)) continue; PropertyDetails details = dict->DetailsAt(i); if (details.IsConfigurable()) return false; if (level == FROZEN && details.kind() == PropertyKind::kData && !details.IsReadOnly()) { return false; } } return true; } bool TestFastPropertiesIntegrityLevel(Tagged<Map> map, PropertyAttributes level) { DCHECK(level == SEALED || level == FROZEN); DCHECK(!IsCustomElementsReceiverMap(map)); DCHECK(!map->is_dictionary_map()); Tagged<DescriptorArray> descriptors = map->instance_descriptors(); for (InternalIndex i : map->IterateOwnDescriptors()) { if (descriptors->GetKey(i)->IsAnyPrivate()) continue; PropertyDetails details = descriptors->GetDetails(i); if (details.IsConfigurable()) return false; if (level == FROZEN && details.kind() == PropertyKind::kData && !details.IsReadOnly()) { return false; } } return true; } bool TestPropertiesIntegrityLevel(Tagged<JSObject> object, PropertyAttributes level) { DCHECK(!IsCustomElementsReceiverMap(object->map())); if (object->HasFastProperties()) { return TestFastPropertiesIntegrityLevel(object->map(), level); } if constexpr (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) { return TestDictionaryPropertiesIntegrityLevel( object->property_dictionary_swiss(), GetReadOnlyRoots(), level); } else { return TestDictionaryPropertiesIntegrityLevel(object->property_dictionary(), GetReadOnlyRoots(), level); } } bool TestElementsIntegrityLevel(Isolate* isolate, Tagged<JSObject> object, PropertyAttributes level) { DCHECK(!object->HasSloppyArgumentsElements()); ElementsKind kind = object->GetElementsKind(); if (IsDictionaryElementsKind(kind)) { return TestDictionaryPropertiesIntegrityLevel( Cast<NumberDictionary>(object->elements()), GetReadOnlyRoots(), level); } if (IsTypedArrayOrRabGsabTypedArrayElementsKind(kind)) { if (level == FROZEN && Cast<JSArrayBufferView>(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(isolate, object) == 0; } bool FastTestIntegrityLevel(Isolate* isolate, Tagged<JSObject> object, PropertyAttributes level) { DCHECK(!IsCustomElementsReceiverMap(object->map())); return !object->map()->is_extensible() && TestElementsIntegrityLevel(isolate, object, level) && TestPropertiesIntegrityLevel(object, level); } } // namespace Maybe<bool> JSObject::TestIntegrityLevel(Isolate* isolate, DirectHandle<JSObject> object, IntegrityLevel level) { if (!IsCustomElementsReceiverMap(object->map()) && !object->HasSloppyArgumentsElements()) { return Just(FastTestIntegrityLevel(isolate, *object, level)); } return GenericTestIntegrityLevel(isolate, Cast<JSReceiver>(object), level); } Maybe<bool> JSObject::PreventExtensions(Isolate* isolate, DirectHandle<JSObject> object, ShouldThrow should_throw) { if (!object->HasSloppyArgumentsElements()) { return PreventExtensionsWithTransition<NONE>(isolate, object, should_throw); } if (IsAccessCheckNeeded(*object) && !isolate->MayAccess(isolate->native_context(), object)) { RETURN_ON_EXCEPTION_VALUE(isolate, isolate->ReportFailedAccessCheck(object), Nothing<bool>()); UNREACHABLE(); } if (!object->map()->is_extensible()) return Just(true); if (IsJSGlobalProxy(*object)) { PrototypeIterator iter(isolate, object); if (iter.IsAtEnd()) return Just(true); DCHECK(IsJSGlobalObject(*PrototypeIterator::GetCurrent(iter))); return PreventExtensions( isolate, 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)); } DCHECK(!object->HasTypedArrayOrRabGsabTypedArrayElements()); // Normalize fast elements. DirectHandle<NumberDictionary> dictionary = NormalizeElements(isolate, 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. DirectHandle<Map> new_map = Map::Copy( isolate, direct_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(Isolate* isolate, DirectHandle<JSObject> object) { if (IsAccessCheckNeeded(*object) && !isolate->MayAccess(isolate->native_context(), object)) { return true; } if (IsJSGlobalProxy(*object)) { PrototypeIterator iter(isolate, *object); if (iter.IsAtEnd()) return false; DCHECK(IsJSGlobalObject(iter.GetCurrent())); return iter.GetCurrent<JSObject>()->map()->is_extensible(); } return object->map()->is_extensible(); } // static MaybeDirectHandle<Object> JSObject::ReadFromOptionsBag( DirectHandle<Object> options, DirectHandle<String> option_name, Isolate* isolate) { if (IsJSReceiver(*options)) { DirectHandle<JSReceiver> js_options = Cast<JSReceiver>(options); return JSObject::GetProperty(isolate, js_options, option_name); } return MaybeDirectHandle<Object>(isolate->factory()->undefined_value()); } template <typename Dictionary> void JSObject::ApplyAttributesToDictionary( Isolate* isolate, ReadOnlyRoots roots, DirectHandle<Dictionary> dictionary, const PropertyAttributes attributes) { for (InternalIndex i : dictionary->IterateEntries()) { Tagged<Object> k; if (!dictionary->ToKey(roots, i, &k)) continue; if (Object::FilterKey(k, 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() == PropertyKind::kAccessor) { Tagged<Object> v = dictionary->ValueAt(i); if (IsAccessorPair(v)) attrs &= ~READ_ONLY; } details = details.CopyAddAttributes(PropertyAttributesFromInt(attrs)); dictionary->DetailsAtPut(i, details); } } template void JSObject::ApplyAttributesToDictionary( Isolate* isolate, ReadOnlyRoots roots, DirectHandle<NumberDictionary> dictionary, const PropertyAttributes attributes); DirectHandle<NumberDictionary> CreateElementDictionary( Isolate* isolate, DirectHandle<JSObject> object) { DirectHandle<NumberDictionary> new_element_dictionary; if (!object->HasTypedArrayOrRabGsabTypedArrayElements() && !object->HasDictionaryElements() && !object->HasSlowStringWrapperElements()) { int length = IsJSArray(*object) ? Smi::ToInt(Cast<JSArray>(object)->length()) : object->elements()->length(); new_element_dictionary = length == 0 ? isolate->factory()->empty_slow_element_dictionary() : object->GetElementsAccessor()->Normalize(isolate, object); } return new_element_dictionary; } template <PropertyAttributes attrs> Maybe<bool> JSObject::PreventExtensionsWithTransition( Isolate* isolate, DirectHandle<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(IsJSModuleNamespace(*object), attrs == NONE); if (IsAccessCheckNeeded(*object) && !isolate->MayAccess(isolate->native_context(), object)) { RETURN_ON_EXCEPTION_VALUE(isolate, isolate->ReportFailedAccessCheck(object), Nothing<bool>()); UNREACHABLE(); } 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 (IsJSGlobalProxy(*object)) { PrototypeIterator iter(isolate, object); if (iter.IsAtEnd()) return Just(true); DCHECK(IsJSGlobalObject(*PrototypeIterator::GetCurrent(iter))); return PreventExtensionsWithTransition<attrs>( isolate, PrototypeIterator::GetCurrent<JSObject>(iter), should_throw); } // Shared objects are designed to have fixed layout, i.e. their maps are // effectively immutable. They are constructed seal, but the semantics of // ordinary ECMAScript objects allow sealed to be upgraded to frozen. This // upgrade violates the fixed layout invariant and is disallowed. if (IsAlwaysSharedSpaceJSObject(*object)) { DCHECK(FastTestIntegrityLevel(isolate, *object, SEALED)); if (attrs != FROZEN) return Just(true); RETURN_FAILURE(isolate, should_throw, NewTypeError(MessageTemplate::kCannotFreeze)); } if (object->map()->has_named_interceptor() || object->map()->has_indexed_interceptor() || (object->HasTypedArrayOrRabGsabTypedArrayElements() && Cast<JSTypedArray>(*object)->IsVariableLength())) { 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)); } DirectHandle<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. switch (object->map()->elements_kind()) { case PACKED_SMI_ELEMENTS: case PACKED_DOUBLE_ELEMENTS: JSObject::TransitionElementsKind(isolate, object, PACKED_ELEMENTS); break; case HOLEY_SMI_ELEMENTS: case HOLEY_DOUBLE_ELEMENTS: JSObject::TransitionElementsKind(isolate, 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. DirectHandle<NumberDictionary> new_element_dictionary; DirectHandle<Map> old_map(object->map(), isolate); old_map = Map::Update(isolate, old_map); DirectHandle<Map> transition_map; MaybeDirectHandle<Map> maybe_transition_map = TransitionsAccessor::SearchSpecial(isolate, old_map, *transition_marker); if (maybe_transition_map.ToHandle(&transition_map)) { DCHECK(transition_map->has_dictionary_elements() || transition_map->has_typed_array_or_rab_gsab_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 (TransitionsAccessor::CanHaveMoreTransitions(isolate, old_map)) { // Create a new descriptor array with the appropriate property attributes DirectHandle<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. DirectHandle<Map> new_map = Map::Copy(isolate, direct_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 (IsJSGlobalObject(*object)) { DirectHandle<GlobalDictionary> dictionary( Cast<JSGlobalObject>(*object)->global_dictionary(kAcquireLoad), isolate); JSObject::ApplyAttributesToDictionary(isolate, roots, dictionary, attrs); } else if constexpr (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) { DirectHandle<SwissNameDictionary> dictionary( object->property_dictionary_swiss(), isolate); JSObject::ApplyAttributesToDictionary(isolate, roots, dictionary, attrs); } else { DirectHandle<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); } // PreventExtensions works without modifications to typed array elements if // the typed array is fixed length; see #sec-typedarray-preventextensions. // Seal and freeze work only if there are no actual elements, because // TypedArray elements cannot be reconfigured; see // #sec-typedarray-defineownproperty. if (object->HasTypedArrayOrRabGsabTypedArrayElements()) { DCHECK(new_element_dictionary.is_null()); if (attrs != NONE && Cast<JSTypedArray>(*object)->GetLength() > 0) { isolate->Throw(*isolate->factory()->NewTypeError( attrs == SEALED ? MessageTemplate::kCannotSealArrayBufferView : 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()) { DirectHandle<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<JSAny> JSObject::FastPropertyAt(Isolate* isolate, DirectHandle<JSObject> object, Representation representation, FieldIndex index) { Handle<JSAny> raw_value(object->RawFastPropertyAt(index), isolate); return Object::WrapForRead(isolate, raw_value, representation); } DirectHandle<JSAny> JSObject::FastPropertyAt(Isolate* isolate, DirectHandle<JSObject> object, Representation representation, FieldIndex index, SeqCstAccessTag tag) { Handle<JSAny> raw_value(object->RawFastPropertyAt(index, tag), isolate); return Object::WrapForRead(isolate, raw_value, representation); } // static Handle<Object> JSObject::DictionaryPropertyAt(Isolate* isolate, DirectHandle<JSObject> object, InternalIndex dict_index) { DCHECK_EQ(ThreadId::Current(), isolate->thread_id()); if constexpr (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) { Tagged<SwissNameDictionary> dict = object->property_dictionary_swiss(); return handle(dict->ValueAt(dict_index), isolate); } else { Tagged<NameDictionary> dict = object->property_dictionary(); return handle(dict->ValueAt(dict_index), isolate); } } // static std::optional<Tagged<Object>> JSObject::DictionaryPropertyAt( DirectHandle<JSObject> object, InternalIndex dict_index, Heap* heap) { Tagged<Object> backing_store = object->raw_properties_or_hash(kRelaxedLoad); if (!IsHeapObject(backing_store)) return {}; if (heap->IsPendingAllocation(Cast<HeapObject>(backing_store))) return {}; if (!IsPropertyDictionary(backing_store)) return {}; std::optional<Tagged<Object>> maybe_obj = Cast<PropertyDictionary>(backing_store)->TryValueAt(dict_index); if (!maybe_obj) return {}; return maybe_obj.value(); } // TODO(cbruni/jkummerow): Consider moving this into elements.cc. bool JSObject::HasEnumerableElements() { // TODO(cbruni): cleanup Tagged<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: case SHARED_ARRAY_ELEMENTS: { int length = IsJSArray(object) ? Smi::ToInt(Cast<JSArray>(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: { Tagged<FixedArray> elements = Cast<FixedArray>(object->elements()); int length = IsJSArray(object) ? Smi::ToInt(Cast<JSArray>(object)->length()) : elements->length(); Isolate* isolate = Isolate::Current(); for (int i = 0; i < length; i++) { if (!elements->is_the_hole(isolate, i)) return true; } return false; } case HOLEY_DOUBLE_ELEMENTS: { int length = IsJSArray(object) ? Smi::ToInt(Cast<JSArray>(object)->length()) : object->elements()->length(); // Zero-length arrays would use the empty FixedArray... if (length == 0) return false; // ...so only cast to FixedDoubleArray otherwise. Tagged<FixedDoubleArray> elements = Cast<FixedDoubleArray>(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) { size_t byte_length = Cast<JSTypedArray>(object)->byte_length(); return byte_length > 0; } RAB_GSAB_TYPED_ARRAYS(TYPED_ARRAY_CASE) #undef TYPED_ARRAY_CASE { size_t length = Cast<JSTypedArray>(object)->GetLength(); return length > 0; } case DICTIONARY_ELEMENTS: { Tagged<NumberDictionary> elements = Cast<NumberDictionary>(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 (Cast<String>(Cast<JSPrimitiveWrapper>(object)->value())->length() > 0) { return true; } return object->elements()->length() > 0; case WASM_ARRAY_ELEMENTS: UNIMPLEMENTED(); case NO_ELEMENTS: return false; } UNREACHABLE(); } MaybeDirectHandle<Object> JSObject::DefineOwnAccessorIgnoreAttributes( DirectHandle<JSObject> object, DirectHandle<Name> name, DirectHandle<Object> getter, DirectHandle<Object> setter, PropertyAttributes attributes) { Isolate* isolate = Isolate::Current(); PropertyKey key(isolate, name); LookupIterator it(isolate, object, key, LookupIterator::OWN_SKIP_INTERCEPTOR); return DefineOwnAccessorIgnoreAttributes(&it, getter, setter, attributes); } MaybeDirectHandle<Object> JSObject::DefineOwnAccessorIgnoreAttributes( LookupIterator* it, DirectHandle<Object> getter, DirectHandle<Object> setter, PropertyAttributes attributes) { Isolate* isolate = it->isolate(); it->UpdateProtector(); while (it->state() == LookupIterator::ACCESS_CHECK) { if (!it->HasAccess()) { RETURN_ON_EXCEPTION( isolate, isolate->ReportFailedAccessCheck(it->GetHolder<JSObject>())); UNREACHABLE(); } it->Next(); } auto object = Cast<JSObject>(it->GetReceiver()); // Ignore accessors on typed arrays. if (it->IsElement() && object->HasTypedArrayOrRabGsabTypedArrayElements()) { return it->factory()->undefined_value(); } DCHECK(IsCallable(*getter) || IsUndefined(*getter, isolate) || IsNull(*getter, isolate) || IsFunctionTemplateInfo(*getter)); DCHECK(IsCallable(*setter) || IsUndefined(*setter, isolate) || IsNull(*setter, isolate) || IsFunctionTemplateInfo(*setter)); it->TransitionToAccessorProperty(getter, setter, attributes); return isolate->factory()->undefined_value(); } MaybeDirectHandle<Object> JSObject::SetAccessor(DirectHandle<JSObject> object, DirectHandle<Name> name, DirectHandle<AccessorInfo> info, PropertyAttributes attributes) { Isolate* isolate = Isolate::Current(); PropertyKey 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. while (it.state() == LookupIterator::ACCESS_CHECK) { if (!it.HasAccess()) { RETURN_ON_EXCEPTION(isolate, isolate->ReportFailedAccessCheck(object)); UNREACHABLE(); } it.Next(); } // Ignore accessors on typed arrays. if (it.IsElement() && object->HasTypedArrayOrRabGsabTypedArrayElements()) { return it.factory()->undefined_value(); } Maybe<bool> can_define = JSObject::CheckIfCanDefineAsConfigurable( isolate, &it, info, Nothing<ShouldThrow>()); MAYBE_RETURN_NULL(can_define); if (!can_define.FromJust()) return it.factory()->undefined_value(); it.TransitionToAccessorPair(info, attributes); return object; } // static Maybe<bool> JSObject::CheckIfCanDefineAsConfigurable( Isolate* isolate, LookupIterator* it, DirectHandle<Object> value, Maybe<ShouldThrow> should_throw) { DCHECK(IsJSObject(*it->GetReceiver())); if (it->IsFound()) { Maybe<PropertyAttributes> attributes = GetPropertyAttributes(it); MAYBE_RETURN(attributes, Nothing<bool>()); if (attributes.FromJust() != ABSENT) { if ((attributes.FromJust() & DONT_DELETE) != 0) { RETURN_FAILURE( isolate, GetShouldThrow(isolate, should_throw), NewTypeError(MessageTemplate::kRedefineDisallowed, it->GetName())); } return Just(true); } // Property does not exist, check object extensibility. } if (!JSObject::IsExtensible(isolate, Cast<JSObject>(it->GetReceiver()))) { RETURN_FAILURE( isolate, GetShouldThrow(isolate, should_throw), NewTypeError(MessageTemplate::kDefineDisallowed, it->GetName())); } return Just(true); } Tagged<Object> JSObject::SlowReverseLookup(Tagged<Object> value) { if (HasFastProperties()) { Tagged<DescriptorArray> descs = map()->instance_descriptors(); bool value_is_number = IsNumber(value); for (InternalIndex i : map()->IterateOwnDescriptors()) { PropertyDetails details = descs->GetDetails(i); if (details.location() == PropertyLocation::kField) { DCHECK_EQ(PropertyKind::kData, details.kind()); FieldIndex field_index = FieldIndex::ForDetails(map(), details); Tagged<Object> property = RawFastPropertyAt(field_index); if (field_index.is_double()) { DCHECK(IsHeapNumber(property)); if (value_is_number && Cast<HeapNumber>(property)->value() == Object::NumberValue(Cast<Number>(value))) { return descs->GetKey(i); } } else if (property == value) { return descs->GetKey(i); } } else { DCHECK_EQ(PropertyLocation::kDescriptor, details.location()); if (details.kind() == PropertyKind::kData) { if (descs->GetStrongValue(i) == value) { return descs->GetKey(i); } } } } return GetReadOnlyRoots().undefined_value(); } else if (IsJSGlobalObject(*this)) { return Cast<JSGlobalObject>(*this) ->global_dictionary(kAcquireLoad) ->SlowReverseLookup(value); } else if constexpr (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) { return property_dictionary_swiss()->SlowReverseLookup(Isolate::Current(), value); } else { return property_dictionary()->SlowReverseLookup(value); } } void JSObject::PrototypeRegistryCompactionCallback(Tagged<HeapObject> value, int old_index, int new_index) { DCHECK(IsMap(value) && Cast<Map>(value)->is_prototype_map()); Tagged<Map> map = Cast<Map>(value); DCHECK(IsPrototypeInfo(map->prototype_info())); Tagged<PrototypeInfo> proto_info = Cast<PrototypeInfo>(map->prototype_info()); DCHECK_EQ(old_index, proto_info->registry_slot()); proto_info->set_registry_slot(new_index); } // static void JSObject::MakePrototypesFast(DirectHandle<Object> receiver, WhereToStart where_to_start, Isolate* isolate) { if (!IsJSReceiver(*receiver)) return; for (PrototypeIterator iter(isolate, Cast<JSReceiver>(receiver), where_to_start); !iter.IsAtEnd(); iter.Advance()) { DirectHandle<Object> current = PrototypeIterator::GetCurrent(iter); #if V8_ENABLE_WEBASSEMBLY if (IsWasmObject(*current)) continue; #endif // V8_ENABLE_WEBASSEMBLY if (!IsJSObjectThatCanBeTrackedAsPrototype(*current)) return; DirectHandle<JSObject> current_obj = Cast<JSObject>(current); Tagged<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; DirectHandle<Map> map(current_map, isolate); Map::SetShouldBeFastPrototypeMap(map, true, isolate); JSObject::OptimizeAsPrototype(current_obj); } } } static bool PrototypeBenefitsFromNormalization(Tagged<JSObject> object) { DisallowGarbageCollection no_gc; if (!object->HasFastProperties()) return false; if (IsJSGlobalProxy(object)) return false; // TODO(v8:11248) make bootstrapper create dict mode prototypes, too? if (Isolate::Current()->bootstrapper()->IsActive()) return false; if (V8_DICT_PROPERTY_CONST_TRACKING_BOOL) return true; return !object->map()->is_prototype_map() || !object->map()->should_be_fast_prototype_map(); } // static void JSObject::OptimizeAsPrototype(DirectHandle<JSObject> object, bool enable_setup_mode) { DCHECK(IsJSObjectThatCanBeTrackedAsPrototype(*object)); if (IsJSGlobalObject(*object)) return; Isolate* isolate = Isolate::Current(); DirectHandle<PrototypeSharedClosureInfo> closure_info; if (v8_flags.proto_assign_seq_lazy_func_opt) { if (Tagged<PrototypeSharedClosureInfo> tagged_closure_info; object->map()->TryGetPrototypeSharedClosureInfo(&tagged_closure_info)) { closure_info = direct_handle(tagged_closure_info, isolate); } } if (object->map()->is_prototype_map()) { if (enable_setup_mode && PrototypeBenefitsFromNormalization(*object)) { // This is the only way PrototypeBenefitsFromNormalization can be true: DCHECK(!object->map()->should_be_fast_prototype_map()); // First normalize to ensure all JSFunctions are DATA_CONSTANT. constexpr bool kUseCache = true; JSObject::NormalizeProperties(isolate, object, KEEP_INOBJECT_PROPERTIES, 0, kUseCache, "NormalizeAsPrototype"); } if (!V8_DICT_PROPERTY_CONST_TRACKING_BOOL && object->map()->should_be_fast_prototype_map() && !object->HasFastProperties()) { JSObject::MigrateSlowToFast(object, 0, "OptimizeAsPrototype"); } } else { DirectHandle<Map> new_map; if (enable_setup_mode && PrototypeBenefitsFromNormalization(*object)) { #if DEBUG DirectHandle<Map> old_map(object->map(isolate), isolate); #endif // DEBUG // First normalize to ensure all JSFunctions are DATA_CONSTANT. Don't use // the cache, since we're going to use the normalized version directly, // without making a copy. constexpr bool kUseCache = false; JSObject::NormalizeProperties(isolate, object, KEEP_INOBJECT_PROPERTIES, 0, kUseCache, "NormalizeAndCopyAsPrototype"); // A new map was created. DCHECK_NE(*old_map, object->map(isolate)); new_map = direct_handle(object->map(isolate), isolate); } else { new_map = Map::Copy(isolate, direct_handle(object->map(), isolate), "CopyAsPrototype"); } new_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. Tagged<Object> maybe_constructor = new_map->GetConstructorRaw(); Tagged<Tuple2> tuple; if (IsTuple2(maybe_constructor)) { // Handle the {constructor, non-instance_prototype} tuple case if the map // has non-instance prototype. tuple = Cast<Tuple2>(maybe_constructor); maybe_constructor = tuple->value1(); } if (IsJSFunction(maybe_constructor)) { Tagged<JSFunction> constructor = Cast<JSFunction>(maybe_constructor); if (!constructor->shared()->IsApiFunction()) { Tagged<NativeContext> context = constructor->native_context(); Tagged<JSFunction> object_function = context->object_function(); if (!tuple.is_null()) { tuple->set_value1(object_function); } else { new_map->SetConstructor(object_function); } } } JSObject::MigrateToMap(isolate, object, new_map); if (V8_DICT_PROPERTY_CONST_TRACKING_BOOL && !object->HasFastProperties()) { ReadOnlyRoots roots(isolate); DisallowHeapAllocation no_gc; auto make_constant = [&](auto dict) { for (InternalIndex index : dict->IterateEntries()) { Tagged<Object> k; if (!dict->ToKey(roots, index, &k)) continue; PropertyDetails details = dict->DetailsAt(index); details = details.CopyWithConstness(PropertyConstness::kConst); dict->DetailsAtPut(index, details); } }; if constexpr (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) { make_constant(object->property_dictionary_swiss()); } else { make_constant(object->property_dictionary()); } } } if (v8_flags.proto_assign_seq_lazy_func_opt) { if (!closure_info.is_null()) { if (Tagged<PrototypeInfo> proto_info; TryCast<PrototypeInfo>( object->map()->prototype_info(), &proto_info)) { proto_info->set_prototype_shared_closure_info(*closure_info); } } } #ifdef DEBUG bool should_be_dictionary = V8_DICT_PROPERTY_CONST_TRACKING_BOOL && enable_setup_mode && !IsJSGlobalProxy(*object) && !isolate->bootstrapper()->IsActive(); DCHECK_IMPLIES(should_be_dictionary, object->map(isolate)->is_dictionary_map()); #endif } // static void JSObject::ReoptimizeIfPrototype(DirectHandle<JSObject> object) { { Tagged<Map> map = object->map(); if (!map->is_prototype_map()) return; if (!map->should_be_fast_prototype_map()) return; } OptimizeAsPrototype(object); } // static void JSObject::LazyRegisterPrototypeUser(DirectHandle<Map> user, Isolate* isolate) { // Contract: In line with InvalidatePrototypeChains()'s requirements, // leaf maps don't need to register as users, only prototypes do. #if V8_ENABLE_WEBASSEMBLY DCHECK(user->is_prototype_map() || IsWasmObjectMap(*user)); #else DCHECK(user->is_prototype_map()); #endif // V8_ENABLE_WEBASSEMBLY DirectHandle<Map> current_user = user; DirectHandle<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; } DirectHandle<Object> maybe_proto = PrototypeIterator::GetCurrent(iter); // This checks for both proxies and shared objects. // // Proxies on the prototype chain are not supported. They make it // impossible to make any assumptions about the prototype chain anyway. // // Objects in the shared heap have fixed layouts and their maps never // change, so they don't need to be tracked as prototypes // anyway. Additionally, registering users of shared objects is not // threadsafe. if (!IsAnyObjectThatCanBeTrackedAsPrototype(*maybe_proto)) continue; DirectHandle<JSReceiver> proto = Cast<JSReceiver>(maybe_proto); DirectHandle<PrototypeInfo> proto_info = Map::GetOrCreatePrototypeInfo(proto, isolate); Handle<Object> maybe_registry(proto_info->prototype_users(), isolate); Handle<WeakArrayList> registry = IsSmi(*maybe_registry) ? handle(ReadOnlyRoots(isolate->heap()).empty_weak_array_list(), isolate) : Cast<WeakArrayList>(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 (v8_flags.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 = direct_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(DirectHandle<Map> user, Isolate* isolate) { DCHECK(user->is_prototype_map()); // If it doesn't have a PrototypeInfo, it was never registered. if (!user->has_prototype_info()) return false; DCHECK(IsPrototypeInfo(user->prototype_info())); // If it had no prototype before, see if it had users that might expect // registration. if (!IsAnyObjectThatCanBeTrackedAsPrototype(user->prototype())) { Tagged<Object> users = Cast<PrototypeInfo>(user->prototype_info())->prototype_users(); return IsWeakArrayList(users); } DirectHandle<JSReceiver> prototype(Cast<JSReceiver>(user->prototype()), isolate); DirectHandle<PrototypeInfo> user_info = Map::GetOrCreatePrototypeInfo(user, isolate); int slot = user_info->registry_slot(); if (slot == PrototypeInfo::UNREGISTERED) return false; #if V8_ENABLE_WEBASSEMBLY DCHECK(prototype->map()->is_prototype_map() || IsWasmObjectMap(prototype->map())); #else DCHECK(prototype->map()->is_prototype_map()); #endif // V8_ENABLE_WEBASSEMBLY Tagged<Object> maybe_proto_info = prototype->map()->prototype_info(); // User knows its registry slot, prototype info and user registry must exist. DCHECK(IsPrototypeInfo(maybe_proto_info)); DirectHandle<PrototypeInfo> proto_info(Cast<PrototypeInfo>(maybe_proto_info), isolate); DirectHandle<WeakArrayList> prototype_users( Cast<WeakArrayList>(proto_info->prototype_users()), isolate); DCHECK_EQ(prototype_users->Get(slot), MakeWeak(*user)); PrototypeUsers::MarkSlotEmpty(*prototype_users, slot); if (v8_flags.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(Tagged<Map> map) { #if V8_ENABLE_WEBASSEMBLY DCHECK(map->is_prototype_map() || IsWasmObjectMap(map)); #else DCHECK(map->is_prototype_map()); #endif // V8_ENABLE_WEBASSEMBLY if (v8_flags.trace_prototype_users) { PrintF("Invalidating prototype map %p 's cell\n", reinterpret_cast<void*>(map.ptr())); } Tagged<Object> maybe_cell = map->prototype_validity_cell(kRelaxedLoad); if (maybe_cell != Map::kNoValidityCellSentinel) { // Just set the value; the cell will be replaced lazily. Tagged<Cell> cell = Cast<Cell>(maybe_cell); if (cell->maybe_value() != Map::kPrototypeChainInvalid) { cell->set_maybe_value(Map::kPrototypeChainInvalid, SKIP_WRITE_BARRIER); } } Tagged<PrototypeInfo> prototype_info; if (map->TryGetPrototypeInfo(&prototype_info)) { prototype_info->set_prototype_chain_enum_cache(Smi::zero()); // Previously created non-existent data handlers might no longer be valid, // ensure they are re-created if necessary. for (int i = 0; i < PrototypeInfo::kCachedHandlerCount; i++) { prototype_info->set_cached_handler(i, Smi::zero(), SKIP_WRITE_BARRIER); } } // We may inline accesses to constants stored in dictionary mode prototypes in // optimized code. When doing so, we install dependencies of group // |kPrototypeCheckGroup| on each prototype between the receiver's immediate // prototype and the holder of the constant property. This dependency is used // both to detect changes to the constant value itself, and other changes to // the prototype chain that invalidate the access to the given property from // the given receiver (like adding the property to another prototype between // the receiver and the (previous) holder). This works by de-opting this group // whenever the validity cell would be invalidated. However, the actual value // of the validity cell is not used. Therefore, we always trigger the de-opt // here, even if the cell was already invalid. if (V8_DICT_PROPERTY_CONST_TRACKING_BOOL && map->is_dictionary_map()) { // TODO(11527): pass Isolate as an argument. Isolate* isolate = Isolate::Current(); DependentCode::DeoptimizeDependencyGroups( isolate, map, DependentCode::kPrototypeCheckGroup); } } void InvalidatePrototypeChainsInternal(Tagged<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. Tagged<Map> next_map; for (; !map.is_null(); map = next_map, next_map = Map()) { InvalidateOnePrototypeValidityCellInternal(map); Tagged<PrototypeInfo> proto_info; if (!map->TryGetPrototypeInfo(&proto_info)) return; if (!IsWeakArrayList(proto_info->prototype_users())) { return; } Tagged<WeakArrayList> prototype_users = Cast<WeakArrayList>(proto_info->prototype_users()); // For now, only maps register themselves as users. for (int i = PrototypeUsers::kFirstIndex; i < prototype_users->length(); ++i) { Tagged<HeapObject> heap_object; if (prototype_users->Get(i).GetHeapObjectIfWeak(&heap_object) && IsMap(heap_object)) { // Walk the prototype chain (backwards, towards leaf objects) if // necessary. if (next_map.is_null()) { next_map = Cast<Map>(heap_object); } else { InvalidatePrototypeChainsInternal(Cast<Map>(heap_object)); } } } } } } // namespace // static Tagged<Map> JSObject::InvalidatePrototypeChains(Tagged<Map> map) { DisallowGarbageCollection 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(Tagged<JSGlobalObject> global) { DisallowGarbageCollection no_gc; InvalidateOnePrototypeValidityCellInternal(global->map()); } Maybe<bool> JSObject::SetPrototype(Isolate* isolate, DirectHandle<JSObject> object, DirectHandle<Object> value_obj, bool from_javascript, ShouldThrow should_throw) { #ifdef DEBUG int size = object->Size(); #endif if (from_javascript) { if (IsAccessCheckNeeded(*object) && !isolate->MayAccess(isolate->native_context(), object)) { RETURN_ON_EXCEPTION_VALUE( isolate, isolate->ReportFailedAccessCheck(object), Nothing<bool>()); UNREACHABLE(); } } else { DCHECK(!IsAccessCheckNeeded(*object)); } // Silently ignore the change if value is not a JSReceiver or null. // SpiderMonkey behaves this way. DirectHandle<JSPrototype> value; if (!TryCast(value_obj, &value)) return Just(true); bool all_extensible = object->map()->is_extensible(); DirectHandle<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(); } } DirectHandle<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) { DirectHandle<Object> msg; if (IsJSObjectPrototype(*object)) { // is [[Object.prototype]] msg = isolate->factory()->Object_prototype_string(); } else { msg = object; } RETURN_FAILURE(isolate, should_throw, NewTypeError(MessageTemplate::kImmutablePrototypeSet, msg)); } // 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 (Tagged<JSReceiver> receiver; TryCast<JSReceiver>(*value, &receiver)) { for (PrototypeIterator iter(isolate, receiver, 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->UpdateProtectorsOnSetPrototype(real_receiver, value); DirectHandle<Map> new_map = MapUpdater(isolate, map).ApplyPrototypeTransition(value); DCHECK(new_map->prototype() == *value); JSObject::MigrateToMap(isolate, real_receiver, new_map); DCHECK_IMPLIES(!new_map->is_dictionary_map() && !map->is_deprecated() && !IsUndefined(new_map->GetBackPointer()), size == object->Size()); return Just(true); } // static void JSObject::SetImmutableProto(Isolate* isolate, DirectHandle<JSObject> object) { DirectHandle<Map> map(object->map(), isolate); // Nothing to do if prototype is already set. if (map->is_immutable_proto()) return; DirectHandle<Map> new_map = Map::TransitionToImmutableProto(isolate, map); object->set_map(isolate, *new_map, kReleaseStore); } void JSObject::EnsureCanContainElements(Isolate* isolate, DirectHandle<JSObject> object, JavaScriptArguments* args, uint32_t arg_count, EnsureElementsMode mode) { return EnsureCanContainElements(isolate, object, FullObjectSlot(args->address_of_arg_at(0)), arg_count, mode); } void JSObject::ValidateElements(Isolate* isolate, Tagged<JSObject> object) { #ifdef ENABLE_SLOW_DCHECKS if (v8_flags.enable_slow_asserts) { object->GetElementsAccessor()->Validate(isolate, 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(Tagged<JSObject> object, Tagged<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 (IsJSArray(object)) { Tagged<Object> length = Cast<JSArray>(object)->length(); if (!IsSmi(length)) return false; *new_capacity = static_cast<uint32_t>(Smi::ToInt(length)); } else if (IsJSArgumentsObject(object)) { 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(Tagged<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()); Tagged<NumberDictionary> dictionary = object->element_dictionary(); ElementsKind kind = HOLEY_SMI_ELEMENTS; for (InternalIndex i : dictionary->IterateEntries()) { Tagged<Object> key; if (!dictionary->ToKey(GetReadOnlyRoots(), i, &key)) continue; if (IsNumber(key)) { Tagged<Object> value = dictionary->ValueAt(i); if (!IsNumber(value)) return HOLEY_ELEMENTS; if (!IsSmi(value)) { if (!v8_flags.unbox_double_arrays) return HOLEY_ELEMENTS; kind = HOLEY_DOUBLE_ELEMENTS; } } } return kind; } // static Maybe<bool> JSObject::AddDataElement(Isolate* isolate, DirectHandle<JSObject> object, uint32_t index, DirectHandle<Object> value, PropertyAttributes attributes) { DCHECK(object->map(isolate)->is_extensible()); uint32_t old_length = 0; uint32_t new_capacity = 0; if (IsJSArray(*object, isolate)) { CHECK(Object::ToArrayLength(Cast<JSArray>(*object)->length(), &old_length)); } ElementsKind kind = object->GetElementsKind(isolate); Tagged<FixedArrayBase> elements = object->elements(isolate); ElementsKind dictionary_kind = DICTIONARY_ELEMENTS; if (IsSloppyArgumentsElementsKind(kind)) { elements = Cast<SloppyArgumentsElements>(elements)->arguments(); dictionary_kind = SLOW_SLOPPY_ARGUMENTS_ELEMENTS; } else if (IsStringWrapperElementsKind(kind)) { dictionary_kind = SLOW_STRING_WRAPPER_ELEMENTS; } if (attributes != NONE) { kind = dictionary_kind; } else if (IsNumberDictionary(elements, isolate)) { kind = ShouldConvertToFastElements( *object, Cast<NumberDictionary>(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 = Object::OptimalElementsKind(*value, isolate); if (IsHoleyElementsKind(kind) || !IsJSArray(*object, isolate) || index > old_length) { to = GetHoleyElementsKind(to); kind = GetHoleyElementsKind(kind); } to = GetMoreGeneralElementsKind(kind, to); ElementsAccessor* accessor = ElementsAccessor::ForKind(to); MAYBE_RETURN( accessor->Add(isolate, object, index, value, attributes, new_capacity), Nothing<bool>()); if (IsJSArray(*object, isolate) && index >= old_length) { DirectHandle<Number> new_length = isolate->factory()->NewNumberFromUint(index + 1); Cast<JSArray>(*object)->set_length(*new_length); } return Just(true); } template <AllocationSiteUpdateMode update_or_check> bool JSObject::UpdateAllocationSite(Isolate* isolate, DirectHandle<JSObject> object, ElementsKind to_kind) { if (!IsJSArray(*object)) return false; if (!HeapLayout::InYoungGeneration(*object)) return false; if (HeapLayout::InAnyLargeSpace(*object)) return false; DirectHandle<AllocationSite> site; { DisallowGarbageCollection no_gc; Heap* heap = isolate->heap(); Tagged<AllocationMemento> memento = PretenuringHandler::FindAllocationMemento< PretenuringHandler::kForRuntime>(heap, object->map(), *object); if (memento.is_null()) return false; // Walk through to the Allocation Site site = direct_handle(memento->GetAllocationSite(), heap->isolate()); } return AllocationSite::DigestTransitionFeedback<update_or_check>( isolate, site, to_kind); } template bool JSObject::UpdateAllocationSite<AllocationSiteUpdateMode::kCheckOnly>( Isolate* isolate, DirectHandle<JSObject> object, ElementsKind to_kind); template bool JSObject::UpdateAllocationSite<AllocationSiteUpdateMode::kUpdate>( Isolate* isolate, DirectHandle<JSObject> object, ElementsKind to_kind); void JSObject::TransitionElementsKind(Isolate* isolate, DirectHandle<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(isolate, object, to_kind); 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. DirectHandle<Map> new_map = GetElementsTransitionMap(isolate, object, to_kind); JSObject::MigrateToMap(isolate, object, new_map); if (v8_flags.trace_elements_transitions) { DirectHandle<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()); if (ElementsAccessor::ForKind(to_kind) ->GrowCapacityAndConvert(isolate, object, c) .IsNothing()) { // TODO(victorgomes): Temporarily forcing a fatal error here in case of // overflow, until all users of TransitionElementsKind can handle // exceptions. FATAL( "Fatal JavaScript invalid size error when transitioning elements " "kind"); UNREACHABLE(); } } } template <typename BackingStore> static int HoleyElementsUsage(Tagged<JSObject> object, Tagged<BackingStore> store) { Isolate* isolate = Isolate::Current(); int limit = IsJSArray(object) ? Smi::ToInt(Cast<JSArray>(object)->length()) : store->length(); int used = 0; for (int i = 0; i < limit; ++i) { if (!store->is_the_hole(isolate, i)) ++used; } return used; } uint32_t JSObject::GetFastElementsUsage() { Tagged<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: case SHARED_ARRAY_ELEMENTS: return IsJSArray(*this) ? Smi::ToInt(Cast<JSArray>(*this)->length()) : store->length(); case FAST_SLOPPY_ARGUMENTS_ELEMENTS: store = Cast<SloppyArgumentsElements>(store)->arguments(); [[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, Cast<FixedArray>(store)); case HOLEY_DOUBLE_ELEMENTS: if (elements()->length() == 0) return 0; return HoleyElementsUsage(*this, Cast<FixedDoubleArray>(store)); case SLOW_SLOPPY_ARGUMENTS_ELEMENTS: case SLOW_STRING_WRAPPER_ELEMENTS: case DICTIONARY_ELEMENTS: case WASM_ARRAY_ELEMENTS: case NO_ELEMENTS: #define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) case TYPE##_ELEMENTS: TYPED_ARRAYS(TYPED_ARRAY_CASE) RAB_GSAB_TYPED_ARRAYS(TYPED_ARRAY_CASE) #undef TYPED_ARRAY_CASE UNREACHABLE(); } return 0; } MaybeHandle<JSAny> JSObject::GetPropertyWithInterceptor(LookupIterator* it, bool* done) { DCHECK_EQ(LookupIterator::INTERCEPTOR, it->state()); return GetPropertyWithInterceptorInternal(it, it->GetInterceptor(), done); } Maybe<bool> JSObject::HasRealNamedProperty(Isolate* isolate, DirectHandle<JSObject> object, DirectHandle<Name> name) { PropertyKey key(isolate, name); LookupIterator it(isolate, object, key, LookupIterator::OWN_SKIP_INTERCEPTOR); return HasProperty(&it); } Maybe<bool> JSObject::HasRealElementProperty(Isolate* isolate, DirectHandle<JSObject> object, uint32_t index) { LookupIterator it(isolate, object, index, object, LookupIterator::OWN_SKIP_INTERCEPTOR); return HasProperty(&it); } Maybe<bool> JSObject::HasRealNamedCallbackProperty( Isolate* isolate, DirectHandle<JSObject> object, DirectHandle<Name> name) { PropertyKey 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>(); } Tagged<Object> JSObject::RawFastPropertyAtCompareAndSwap( FieldIndex index, Tagged<Object> expected, Tagged<Object> value, SeqCstAccessTag tag) { return HeapObject::SeqCst_CompareAndSwapField( expected, value, [=, this](Tagged<Object> expected_value, Tagged<Object> new_value) { return RawFastPropertyAtCompareAndSwapInternal(index, expected_value, new_value, tag); }); } bool JSGlobalProxy::IsDetached() { return !GetCreationContext().has_value(); } void JSGlobalObject::InvalidatePropertyCell(DirectHandle<JSGlobalObject> global, DirectHandle<Name> name) { Isolate* isolate = Isolate::Current(); // 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 = direct_handle(global->global_dictionary(kAcquireLoad), isolate); InternalIndex entry = dictionary->FindEntry(isolate, name); if (entry.is_not_found()) return; DirectHandle<PropertyCell> cell(dictionary->CellAt(entry), isolate); DirectHandle<Object> value(cell->value(), isolate); PropertyDetails details = cell->property_details(); details = details.set_cell_type(PropertyCellType::kMutable); PropertyCell::InvalidateAndReplaceEntry(isolate, dictionary, entry, details, value); } // static Maybe<bool> JSGlobalObject::HasRestrictedGlobalProperty( Isolate* isolate, DirectHandle<JSGlobalObject> global, DirectHandle<Name> name) { LookupIterator::Configuration config = LookupIterator::OWN_SKIP_INTERCEPTOR; if (global->HasNamedInterceptor() && global->GetNamedInterceptor()->has_dont_delete_property()) { config = LookupIterator::OWN; } LookupIterator it(isolate, global, name, global, config); Maybe<PropertyAttributes> maybe = JSReceiver::GetPropertyAttributes(&it); if (maybe.IsNothing()) return Nothing<bool>(); // Global var and function bindings (except those that are introduced by // non-strict direct eval) are non-configurable and are therefore restricted // global properties. return Just((maybe.FromJust() & DONT_DELETE) != 0); } // static MaybeDirectHandle<JSDate> JSDate::New(Isolate* isolate, DirectHandle<JSFunction> constructor, DirectHandle<JSReceiver> new_target, double tv) { DirectHandle<JSDate> result; ASSIGN_RETURN_ON_EXCEPTION( isolate, result, Cast<JSDate>(JSObject::New(constructor, new_target, {}))); if (DateCache::TryTimeClip(&tv)) { result->SetValue(isolate, tv); } else { result->SetNanValue(); } return result; } // static int64_t JSDate::CurrentTimeValue(Isolate* isolate) { if (v8_flags.log_timer_events) LOG(isolate, CurrentTimeEvent()); if (v8_flags.correctness_fuzzer_suppressions) return 4; // 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 V8::GetCurrentPlatform()->CurrentClockTimeMilliseconds(); } // static Address JSDate::GetField(Isolate* isolate, Address raw_object, Address smi_index) { // Called through CallCFunction. DisallowGarbageCollection no_gc; DisallowHandleAllocation no_handles; DisallowJavascriptExecution no_js(isolate); Tagged<Object> object(raw_object); Tagged<Smi> index(smi_index); return Cast<JSDate>(object) ->DoGetField(isolate, static_cast<FieldIndex>(index.value())) .ptr(); } Tagged<Object> JSDate::DoGetField(Isolate* isolate, FieldIndex index) { DateCache* date_cache = isolate->date_cache(); if (index < kFirstUncachedField) { Tagged<Object> stamp = cache_stamp(); if (stamp != isolate->date_cache_stamp() && IsSmi(stamp)) { // 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())); SetCachedFields(isolate, 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(), date_cache); } double time = value(); 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); } Tagged<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 void JSDate::SetValue(Isolate* isolate, double value) { #ifdef DEBUG DCHECK(!std::isnan(value)); double clipped_value = value; DCHECK(DateCache::TryTimeClip(&clipped_value)); DCHECK_EQ(value, clipped_value); #endif set_value(value); DateCache* date_cache = isolate->date_cache(); // 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)); SetCachedFields(isolate, local_time_ms, date_cache); } void JSDate::SetNanValue() { set_value(std::numeric_limits<double>::quiet_NaN()); Tagged<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); } void JSDate::UpdateFieldsAfterDeserialization(Isolate* isolate) { double time = value(); if (std::isnan(time)) { // In this case all the cached fields are set to NaNs, thus no updates // necessary. return; } DateCache* date_cache = isolate->date_cache(); int64_t local_time_ms = date_cache->ToLocal(static_cast<int64_t>(time)); SetCachedFields(isolate, local_time_ms, date_cache); } void JSDate::SetCachedFields(Isolate* isolate, 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(isolate->GetDateCacheStampAndRecordUsage(), SKIP_WRITE_BARRIER); 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::InitializeSourcePositions( Isolate* isolate, DirectHandle<JSMessageObject> message) { DCHECK(!message->DidEnsureSourcePositionsAvailable()); Script::InitLineEnds(isolate, direct_handle(message->script(), isolate)); if (message->shared_info() == Smi::FromInt(-1)) { message->set_shared_info(Smi::zero()); return; } DCHECK(IsSharedFunctionInfo(message->shared_info())); DCHECK_GE(message->bytecode_offset().value(), kFunctionEntryBytecodeOffset); Handle<SharedFunctionInfo> shared_info( Cast<SharedFunctionInfo>(message->shared_info()), isolate); IsCompiledScope is_compiled_scope; SharedFunctionInfo::EnsureBytecodeArrayAvailable( isolate, shared_info, &is_compiled_scope, CreateSourcePositions::kYes); SharedFunctionInfo::EnsureSourcePositionsAvailable(isolate, shared_info); DCHECK(shared_info->HasBytecodeArray()); int position = shared_info->abstract_code(isolate)->SourcePosition( isolate, message->bytecode_offset().value()); DCHECK_GE(position, 0); message->set_start_position(position); message->set_end_position(position + 1); message->set_shared_info(Smi::zero()); } int JSMessageObject::GetLineNumber() const { DisallowGarbageCollection no_gc; DCHECK(DidEnsureSourcePositionsAvailable()); if (start_position() == -1) return Message::kNoLineNumberInfo; DCHECK(script()->has_line_ends()); DirectHandle<Script> the_script(script(), Isolate::Current()); Script::PositionInfo info; if (!script()->GetPositionInfo(start_position(), &info)) { return Message::kNoLineNumberInfo; } return info.line + 1; } int JSMessageObject::GetColumnNumber() const { DisallowGarbageCollection no_gc; DCHECK(DidEnsureSourcePositionsAvailable()); if (start_position() == -1) return -1; DCHECK(script()->has_line_ends()); DirectHandle<Script> the_script(script(), Isolate::Current()); Script::PositionInfo info; if (!script()->GetPositionInfo(start_position(), &info)) { return -1; } return info.column; // Note: No '+1' in contrast to GetLineNumber. } Tagged<String> JSMessageObject::GetSource() const { DisallowGarbageCollection no_gc; Tagged<Script> script_object = script(); if (script_object->HasValidSource()) { Tagged<Object> source = script_object->source(); if (IsString(source)) return Cast<String>(source); } return ReadOnlyRoots(Isolate::Current()).empty_string(); } DirectHandle<String> JSMessageObject::GetSourceLine() const { Isolate* isolate = Isolate::Current(); #if V8_ENABLE_WEBASSEMBLY if (script()->type() == Script::Type::kWasm) { return isolate->factory()->empty_string(); } #endif // V8_ENABLE_WEBASSEMBLY Script::PositionInfo info; { DisallowGarbageCollection no_gc; DCHECK(DidEnsureSourcePositionsAvailable()); DCHECK(script()->has_line_ends()); if (!script()->GetPositionInfo(start_position(), &info)) { return isolate->factory()->empty_string(); } } Handle<String> src(Cast<String>(script()->source()), isolate); return isolate->factory()->NewSubString(src, info.line_start, info.line_end); } } // namespace v8::internal