| // Copyright 2014 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/ast/ast.h" |
| #include "src/base/logging.h" |
| #include "src/common/globals.h" |
| #include "src/execution/arguments-inl.h" |
| #include "src/execution/isolate-inl.h" |
| #include "src/handles/maybe-handles.h" |
| #include "src/objects/allocation-site-scopes-inl.h" |
| #include "src/objects/casting.h" |
| #include "src/objects/dictionary-inl.h" |
| #include "src/objects/field-type.h" |
| #include "src/objects/hash-table-inl.h" |
| #include "src/objects/heap-number-inl.h" |
| #include "src/objects/heap-object.h" |
| #include "src/objects/js-data-object-builder-inl.h" |
| #include "src/objects/js-regexp-inl.h" |
| #include "src/objects/literal-objects-inl.h" |
| #include "src/objects/lookup.h" |
| #include "src/objects/map-updater.h" |
| #include "src/objects/object-conversions-inl.h" |
| #include "src/objects/objects.h" |
| #include "src/objects/property-descriptor-object.h" |
| #include "src/objects/property-descriptor.h" |
| #include "src/objects/property-details.h" |
| #include "src/objects/transitions-inl.h" |
| #include "src/runtime/runtime.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| namespace { |
| |
| bool IsUninitializedLiteralSite(Tagged<Object> literal_site) { |
| return literal_site == Smi::zero(); |
| } |
| |
| bool HasBoilerplate(DirectHandle<Object> literal_site) { |
| return !IsSmi(*literal_site); |
| } |
| |
| void PreInitializeLiteralSite(DirectHandle<FeedbackVector> vector, |
| FeedbackSlot slot) { |
| vector->SynchronizedSet(slot, Smi::FromInt(1)); |
| } |
| |
| template <class ContextObject> |
| class JSObjectWalkVisitor { |
| public: |
| explicit JSObjectWalkVisitor(ContextObject* site_context) |
| : site_context_(site_context) {} |
| |
| V8_WARN_UNUSED_RESULT MaybeHandle<JSObject> StructureWalk( |
| Handle<JSObject> object); |
| |
| protected: |
| V8_WARN_UNUSED_RESULT inline MaybeHandle<JSObject> VisitElementOrProperty( |
| DirectHandle<JSObject> object, Handle<JSObject> value) { |
| // Dont create allocation sites for nested object literals |
| if (!IsJSArray(*value)) { |
| return StructureWalk(value); |
| } |
| |
| DirectHandle<AllocationSite> current_site = site_context()->EnterNewScope(); |
| MaybeHandle<JSObject> copy_of_value = StructureWalk(value); |
| site_context()->ExitScope(current_site, value); |
| return copy_of_value; |
| } |
| |
| inline ContextObject* site_context() { return site_context_; } |
| inline Isolate* isolate() { return site_context()->isolate(); } |
| |
| private: |
| ContextObject* site_context_; |
| }; |
| |
| template <class ContextObject> |
| MaybeHandle<JSObject> JSObjectWalkVisitor<ContextObject>::StructureWalk( |
| Handle<JSObject> object) { |
| Isolate* isolate = this->isolate(); |
| bool copying = ContextObject::kCopying; |
| |
| { |
| StackLimitCheck check(isolate); |
| |
| if (check.HasOverflowed()) { |
| isolate->StackOverflow(); |
| return MaybeHandle<JSObject>(); |
| } |
| } |
| |
| if (object->map()->is_deprecated()) { |
| base::MutexGuard mutex_guard(isolate->boilerplate_migration_access()); |
| JSObject::MigrateInstance(isolate, object); |
| } |
| |
| Handle<JSObject> copy; |
| if (copying) { |
| // JSFunction objects are not allowed to be in normal boilerplates at all. |
| DCHECK(!IsJSFunction(*object)); |
| DirectHandle<AllocationSite> site_to_pass; |
| if (site_context()->ShouldCreateMemento(object)) { |
| site_to_pass = site_context()->current(); |
| } |
| copy = isolate->factory()->CopyJSObjectWithAllocationSite(object, |
| site_to_pass); |
| } else { |
| copy = object; |
| } |
| |
| DCHECK(copying || copy.is_identical_to(object)); |
| |
| HandleScope scope(isolate); |
| |
| // Deep copy own properties. Arrays only have 1 property "length". |
| if (!IsJSArray(*copy)) { |
| if (copy->HasFastProperties()) { |
| DirectHandle<DescriptorArray> descriptors( |
| copy->map()->instance_descriptors(), isolate); |
| for (InternalIndex i : copy->map()->IterateOwnDescriptors()) { |
| PropertyDetails details = descriptors->GetDetails(i); |
| DCHECK_EQ(PropertyLocation::kField, details.location()); |
| DCHECK_EQ(PropertyKind::kData, details.kind()); |
| FieldIndex index = FieldIndex::ForDetails(copy->map(), details); |
| |
| Tagged<Object> raw = copy->RawFastPropertyAt(index); |
| if (IsJSObject(raw)) { |
| Handle<JSObject> value(Cast<JSObject>(raw), isolate); |
| ASSIGN_RETURN_ON_EXCEPTION(isolate, value, |
| VisitElementOrProperty(copy, value)); |
| if (copying) copy->FastPropertyAtPut(index, *value); |
| } else if (copying && details.representation().IsDouble()) { |
| uint64_t double_value = Cast<HeapNumber>(raw)->value_as_bits(); |
| auto value = isolate->factory()->NewHeapNumberFromBits(double_value); |
| copy->FastPropertyAtPut(index, *value); |
| } |
| } |
| } else { |
| if (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) { |
| DirectHandle<SwissNameDictionary> dict( |
| copy->property_dictionary_swiss(), isolate); |
| for (InternalIndex i : dict->IterateEntries()) { |
| Tagged<Object> raw = dict->ValueAt(i); |
| if (!IsJSObject(raw)) continue; |
| DCHECK(IsName(dict->KeyAt(i))); |
| Handle<JSObject> value(Cast<JSObject>(raw), isolate); |
| ASSIGN_RETURN_ON_EXCEPTION(isolate, value, |
| VisitElementOrProperty(copy, value)); |
| if (copying) dict->ValueAtPut(i, *value); |
| } |
| } else { |
| DirectHandle<NameDictionary> dict(copy->property_dictionary(), isolate); |
| for (InternalIndex i : dict->IterateEntries()) { |
| Tagged<Object> raw = dict->ValueAt(i); |
| if (!IsJSObject(raw)) continue; |
| DCHECK(IsName(dict->KeyAt(i))); |
| Handle<JSObject> value(Cast<JSObject>(raw), isolate); |
| ASSIGN_RETURN_ON_EXCEPTION(isolate, value, |
| VisitElementOrProperty(copy, value)); |
| if (copying) dict->ValueAtPut(i, *value); |
| } |
| } |
| } |
| |
| // Assume non-arrays don't end up having elements. |
| if (copy->elements()->ulength().value() == 0) return copy; |
| } |
| |
| // Deep copy own elements. |
| switch (copy->GetElementsKind()) { |
| case PACKED_ELEMENTS: |
| case PACKED_FROZEN_ELEMENTS: |
| case PACKED_SEALED_ELEMENTS: |
| case PACKED_NONEXTENSIBLE_ELEMENTS: |
| case HOLEY_FROZEN_ELEMENTS: |
| case HOLEY_SEALED_ELEMENTS: |
| case HOLEY_NONEXTENSIBLE_ELEMENTS: |
| case HOLEY_ELEMENTS: |
| case SHARED_ARRAY_ELEMENTS: { |
| DirectHandle<FixedArray> elements(Cast<FixedArray>(copy->elements()), |
| isolate); |
| uint32_t elements_len = elements->ulength().value(); |
| if (elements->map() == ReadOnlyRoots(isolate).fixed_cow_array_map()) { |
| #ifdef DEBUG |
| for (uint32_t i = 0; i < elements_len; i++) { |
| DCHECK(!IsJSObject(elements->get(i))); |
| } |
| #endif |
| } else { |
| for (uint32_t i = 0; i < elements_len; i++) { |
| Tagged<Object> raw = elements->get(i); |
| if (!IsJSObject(raw)) continue; |
| Handle<JSObject> value(Cast<JSObject>(raw), isolate); |
| ASSIGN_RETURN_ON_EXCEPTION(isolate, value, |
| VisitElementOrProperty(copy, value)); |
| if (copying) elements->set(i, *value); |
| } |
| } |
| break; |
| } |
| case DICTIONARY_ELEMENTS: { |
| DirectHandle<NumberDictionary> element_dictionary( |
| copy->element_dictionary(), isolate); |
| for (InternalIndex i : element_dictionary->IterateEntries()) { |
| Tagged<Object> raw = element_dictionary->ValueAt(i); |
| if (!IsJSObject(raw)) continue; |
| Handle<JSObject> value(Cast<JSObject>(raw), isolate); |
| ASSIGN_RETURN_ON_EXCEPTION(isolate, value, |
| VisitElementOrProperty(copy, value)); |
| if (copying) element_dictionary->ValueAtPut(i, *value); |
| } |
| break; |
| } |
| case FAST_SLOPPY_ARGUMENTS_ELEMENTS: |
| case SLOW_SLOPPY_ARGUMENTS_ELEMENTS: |
| UNIMPLEMENTED(); |
| break; |
| case FAST_STRING_WRAPPER_ELEMENTS: |
| case SLOW_STRING_WRAPPER_ELEMENTS: |
| case WASM_ARRAY_ELEMENTS: |
| UNREACHABLE(); |
| |
| #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 |
| // Typed elements cannot be created using an object literal. |
| UNREACHABLE(); |
| |
| case PACKED_SMI_ELEMENTS: |
| case HOLEY_SMI_ELEMENTS: |
| case PACKED_DOUBLE_ELEMENTS: |
| case HOLEY_DOUBLE_ELEMENTS: |
| case NO_ELEMENTS: |
| // No contained objects, nothing to do. |
| break; |
| } |
| |
| return copy; |
| } |
| |
| // AllocationSiteCreationContext aids in the creation of AllocationSites to |
| // accompany object literals. |
| class AllocationSiteCreationContext : public AllocationSiteContext { |
| public: |
| explicit AllocationSiteCreationContext(Isolate* isolate) |
| : AllocationSiteContext(isolate) {} |
| |
| Handle<AllocationSite> EnterNewScope() { |
| Handle<AllocationSite> scope_site; |
| if (top().is_null()) { |
| // We are creating the top level AllocationSite as opposed to a nested |
| // AllocationSite. |
| InitializeTraversal(isolate()->factory()->NewAllocationSite(true)); |
| scope_site = Handle<AllocationSite>(*top(), isolate()); |
| if (v8_flags.trace_creation_allocation_sites) { |
| PrintF("*** Creating top level %s AllocationSite %p\n", "Fat", |
| reinterpret_cast<void*>(scope_site->ptr())); |
| } |
| } else { |
| DCHECK(!current().is_null()); |
| scope_site = isolate()->factory()->NewAllocationSite(false); |
| if (v8_flags.trace_creation_allocation_sites) { |
| PrintF( |
| "*** Creating nested %s AllocationSite (top, current, new) (%p, " |
| "%p, " |
| "%p)\n", |
| "Slim", reinterpret_cast<void*>(top()->ptr()), |
| reinterpret_cast<void*>(current()->ptr()), |
| reinterpret_cast<void*>(scope_site->ptr())); |
| } |
| current()->set_nested_site(*scope_site); |
| update_current_site(*scope_site); |
| } |
| DCHECK(!scope_site.is_null()); |
| return scope_site; |
| } |
| void ExitScope(DirectHandle<AllocationSite> scope_site, |
| DirectHandle<JSObject> object) { |
| if (object.is_null()) return; |
| scope_site->set_boilerplate(*object, kReleaseStore); |
| if (v8_flags.trace_creation_allocation_sites) { |
| bool top_level = |
| !scope_site.is_null() && top().is_identical_to(scope_site); |
| if (top_level) { |
| PrintF("*** Setting AllocationSite %p transition_info %p\n", |
| reinterpret_cast<void*>(scope_site->ptr()), |
| reinterpret_cast<void*>(object->ptr())); |
| } else { |
| PrintF("*** Setting AllocationSite (%p, %p) transition_info %p\n", |
| reinterpret_cast<void*>(top()->ptr()), |
| reinterpret_cast<void*>(scope_site->ptr()), |
| reinterpret_cast<void*>(object->ptr())); |
| } |
| } |
| } |
| static const bool kCopying = false; |
| }; |
| |
| MaybeDirectHandle<JSObject> DeepWalk( |
| Handle<JSObject> object, AllocationSiteCreationContext* site_context) { |
| JSObjectWalkVisitor<AllocationSiteCreationContext> v(site_context); |
| MaybeDirectHandle<JSObject> result = v.StructureWalk(object); |
| DirectHandle<JSObject> for_assert; |
| DCHECK(!result.ToHandle(&for_assert) || for_assert.is_identical_to(object)); |
| return result; |
| } |
| |
| MaybeDirectHandle<JSObject> DeepCopy(Handle<JSObject> object, |
| AllocationSiteUsageContext* site_context) { |
| JSObjectWalkVisitor<AllocationSiteUsageContext> v(site_context); |
| MaybeDirectHandle<JSObject> copy = v.StructureWalk(object); |
| DirectHandle<JSObject> for_assert; |
| DCHECK(!copy.ToHandle(&for_assert) || !for_assert.is_identical_to(object)); |
| return copy; |
| } |
| |
| Handle<JSObject> CreateObjectLiteral( |
| Isolate* isolate, |
| DirectHandle<ObjectBoilerplateDescription> object_boilerplate_description, |
| int flags, AllocationType allocation); |
| |
| Handle<JSObject> CreateArrayLiteral( |
| Isolate* isolate, |
| DirectHandle<ArrayBoilerplateDescription> array_boilerplate_description, |
| AllocationType allocation); |
| |
| struct ObjectLiteralHelper { |
| static inline Handle<JSObject> Create(Isolate* isolate, |
| Handle<HeapObject> description, |
| int flags, AllocationType allocation) { |
| auto object_boilerplate_description = |
| Cast<ObjectBoilerplateDescription>(description); |
| return CreateObjectLiteral(isolate, object_boilerplate_description, flags, |
| allocation); |
| } |
| }; |
| |
| struct ArrayLiteralHelper { |
| static inline Handle<JSObject> Create(Isolate* isolate, |
| Handle<HeapObject> description, |
| int flags_not_used, |
| AllocationType allocation) { |
| auto array_boilerplate_description = |
| Cast<ArrayBoilerplateDescription>(description); |
| return CreateArrayLiteral(isolate, array_boilerplate_description, |
| allocation); |
| } |
| }; |
| |
| Handle<JSObject> CreateObjectLiteralWithNullProto( |
| Isolate* isolate, |
| DirectHandle<ObjectBoilerplateDescription> object_boilerplate_description, |
| int flags, AllocationType allocation) { |
| DirectHandle<NativeContext> native_context = isolate->native_context(); |
| bool use_fast_elements = (flags & ObjectLiteral::kFastElements) != 0; |
| int number_of_properties = |
| object_boilerplate_description->backing_store_size(); |
| |
| // Ignoring number_of_properties for force dictionary map with |
| // __proto__:null. |
| DirectHandle<Map> map = direct_handle( |
| native_context->slow_object_with_null_prototype_map(), isolate); |
| Handle<JSObject> boilerplate = |
| isolate->factory()->NewFastOrSlowJSObjectFromMap( |
| map, number_of_properties, allocation); |
| |
| // Normalize the elements of the boilerplate to save space if needed. |
| if (!use_fast_elements) JSObject::NormalizeElements(isolate, boilerplate); |
| |
| // TODO(leszeks): This path could be faster, e.g. we could populate the |
| // property dictionary directly. |
| int length = object_boilerplate_description->boilerplate_properties_count(); |
| for (int index = 0; index < length; index++) { |
| DirectHandle<ObjectBoilerplateDescription::KeyT> key( |
| object_boilerplate_description->name(index), isolate); |
| DirectHandle<Object> value(object_boilerplate_description->value(index), |
| isolate); |
| if (DirectHandle<HeapObject> ho_value; TryCast(value, &ho_value)) { |
| if (DirectHandle<ArrayBoilerplateDescription> array_desc; |
| TryCast(ho_value, &array_desc)) { |
| value = CreateArrayLiteral(isolate, array_desc, allocation); |
| } else if (DirectHandle<ObjectBoilerplateDescription> obj_desc; |
| TryCast(ho_value, &obj_desc)) { |
| value = CreateObjectLiteral(isolate, obj_desc, obj_desc->flags(), |
| allocation); |
| } |
| } |
| |
| uint32_t element_index = 0; |
| if (Object::ToArrayIndex(*key, &element_index)) { |
| // Array index (uint32). |
| JSObject::SetOwnElementIgnoreAttributes(boilerplate, element_index, value, |
| NONE) |
| .Check(); |
| } else { |
| DirectHandle<String> name = Cast<InternalizedString>(key); |
| DCHECK(!name->AsArrayIndex(&element_index)); |
| JSObject::SetOwnPropertyIgnoreAttributes(boilerplate, name, value, NONE) |
| .Check(); |
| } |
| } |
| return boilerplate; |
| } |
| |
| // Helper class for iterating an ObjectBoilerplateDescription for use with |
| // JSDataObjectBuilder. |
| class ObjectBoilerplateDescriptionIterator { |
| public: |
| static constexpr bool kSupportsRawKeys = false; |
| static constexpr bool kMayHaveDuplicateKeys = false; |
| |
| ObjectBoilerplateDescriptionIterator( |
| DirectHandle<ObjectBoilerplateDescription> object_boilerplate_description, |
| Isolate* isolate, AllocationType allocation) |
| : object_boilerplate_description_(object_boilerplate_description), |
| isolate_(isolate), |
| allocation_(allocation) { |
| for (; i_ < length_; ++i_) { |
| if (IsInternalizedString(object_boilerplate_description->name(i_))) { |
| break; |
| } |
| } |
| start_ = i_; |
| } |
| |
| Handle<InternalizedString> GetKey() { |
| return handle( |
| Cast<InternalizedString>(object_boilerplate_description_->name(i_)), |
| isolate_); |
| } |
| |
| Handle<Object> GetValue(bool will_revisit) { |
| Handle<Object> value(object_boilerplate_description_->value(i_), isolate_); |
| if (Handle<HeapObject> ho_value; TryCast(value, &ho_value)) { |
| if (Handle<ArrayBoilerplateDescription> array_desc; |
| TryCast(ho_value, &array_desc)) { |
| value = CreateArrayLiteral(isolate_, array_desc, allocation_); |
| if (will_revisit) { |
| materialized_values_.push_back(value); |
| } |
| } else if (Handle<ObjectBoilerplateDescription> obj_desc; |
| TryCast(ho_value, &obj_desc)) { |
| value = CreateObjectLiteral(isolate_, obj_desc, obj_desc->flags(), |
| allocation_); |
| if (will_revisit) { |
| materialized_values_.push_back(value); |
| } |
| } |
| } |
| return value; |
| } |
| |
| void Advance() { |
| if (i_ >= length_) return; |
| for (++i_; i_ < length_; ++i_) { |
| if (IsInternalizedString(object_boilerplate_description_->name(i_))) { |
| break; |
| } |
| } |
| } |
| |
| bool Done() { return i_ >= length_; } |
| |
| // Helper class for revisiting values already iterated by this iterator, in |
| // particular avoiding re-materialising nested values. |
| struct RevisitValueIterator { |
| DirectHandle<ObjectBoilerplateDescription> object_boilerplate_description; |
| base::SmallVector<Handle<Object>, 4>::iterator materialized_values_it; |
| base::SmallVector<Handle<Object>, 4>::iterator materialized_values_end; |
| Isolate* isolate; |
| int i; |
| int length = object_boilerplate_description->boilerplate_properties_count(); |
| |
| Tagged<Object> GetNext() { |
| DCHECK(IsInternalizedString(object_boilerplate_description->name(i))); |
| |
| Tagged<Object> value = object_boilerplate_description->value(i); |
| |
| // Potentially re-use an already materialized value. |
| if (IsArrayBoilerplateDescription(value) || |
| IsObjectBoilerplateDescription(value)) { |
| SBXCHECK_LT(materialized_values_it, materialized_values_end); |
| value = **materialized_values_it++; |
| } |
| |
| // Advance to the next string name. |
| for (++i; i < length; ++i) { |
| if (IsInternalizedString(object_boilerplate_description->name(i))) { |
| break; |
| } |
| } |
| return value; |
| } |
| }; |
| |
| RevisitValueIterator RevisitValues() { |
| return RevisitValueIterator{object_boilerplate_description_, |
| materialized_values_.begin(), |
| materialized_values_.end(), isolate_, start_}; |
| } |
| |
| private: |
| DirectHandle<ObjectBoilerplateDescription> object_boilerplate_description_; |
| Isolate* isolate_; |
| AllocationType allocation_; |
| int i_ = 0; |
| int start_ = 0; |
| int length_ = object_boilerplate_description_->boilerplate_properties_count(); |
| base::SmallVector<Handle<Object>, 4> materialized_values_ = {}; |
| }; |
| |
| Handle<JSObject> CreateObjectLiteral( |
| Isolate* isolate, |
| DirectHandle<ObjectBoilerplateDescription> object_boilerplate_description, |
| int flags, AllocationType allocation) { |
| DirectHandle<NativeContext> native_context = isolate->native_context(); |
| bool has_null_prototype = (flags & ObjectLiteral::kHasNullPrototype) != 0; |
| |
| if (has_null_prototype) { |
| return CreateObjectLiteralWithNullProto( |
| isolate, object_boilerplate_description, flags, allocation); |
| } |
| // Fast path using manual elements initialisation and JSDataObjectBuilder. |
| |
| bool use_fast_elements = (flags & ObjectLiteral::kFastElements) != 0; |
| int number_of_properties = |
| object_boilerplate_description->backing_store_size(); |
| |
| int length = object_boilerplate_description->boilerplate_properties_count(); |
| |
| // First iterate to count the elements and find the max element index. |
| // TODO(leszeks): We could already at parse-time figure out if there are any |
| // elements and avoid this step. |
| int element_count = 0; |
| uint32_t max_element_index = 0; |
| for (int i = 0; i < length; i++) { |
| uint32_t element_index = 0; |
| if (Object::ToArrayIndex(object_boilerplate_description->name(i), |
| &element_index)) { |
| element_count++; |
| if (element_index > max_element_index) max_element_index = element_index; |
| } |
| } |
| |
| // Then allocate the appropriate element container. |
| DirectHandle<FixedArray> elements; |
| ElementsKind elements_kind; |
| if (use_fast_elements) { |
| elements_kind = HOLEY_ELEMENTS; |
| if (element_count > 0) { |
| elements = |
| isolate->factory()->NewFixedArrayWithHoles(max_element_index + 1); |
| } else { |
| elements = isolate->factory()->empty_fixed_array(); |
| } |
| } else { |
| elements_kind = DICTIONARY_ELEMENTS; |
| elements = NumberDictionary::New(isolate, element_count); |
| } |
| |
| // Fill the element container with values. |
| for (int i = 0; i < length; i++) { |
| uint32_t element_index = 0; |
| if (!Object::ToArrayIndex(object_boilerplate_description->name(i), |
| &element_index)) { |
| continue; |
| } |
| |
| DirectHandle<Object> value = |
| direct_handle(object_boilerplate_description->value(i), isolate); |
| |
| if (DirectHandle<HeapObject> ho_value; TryCast(value, &ho_value)) { |
| if (DirectHandle<ArrayBoilerplateDescription> array_desc; |
| TryCast(ho_value, &array_desc)) { |
| value = CreateArrayLiteral(isolate, array_desc, allocation); |
| } else if (DirectHandle<ObjectBoilerplateDescription> obj_desc; |
| TryCast(ho_value, &obj_desc)) { |
| value = CreateObjectLiteral(isolate, obj_desc, obj_desc->flags(), |
| allocation); |
| } |
| } |
| |
| if (use_fast_elements) { |
| elements->set(element_index, *value); |
| } else { |
| DirectHandle<NumberDictionary> dict = Cast<NumberDictionary>(elements); |
| PropertyDetails details(PropertyKind::kData, NONE, |
| PropertyConstness::kConst, 0); |
| NumberDictionary::UncheckedAdd(isolate, dict, element_index, value, |
| details); |
| } |
| } |
| |
| if (!use_fast_elements) { |
| DirectHandle<NumberDictionary> dict = Cast<NumberDictionary>(elements); |
| dict->SetInitialNumberOfElements(element_count); |
| dict->UpdateMaxNumberKey(max_element_index, Handle<JSObject>::null()); |
| } |
| |
| // Finally, use JSDataObjectBuilder to build the object itself. |
| JSDataObjectBuilder builder(isolate, elements_kind, number_of_properties, |
| DirectHandle<Map>(), |
| JSDataObjectBuilder::kNormalHeapNumbers); |
| |
| return builder.BuildFromIterator( |
| ObjectBoilerplateDescriptionIterator{object_boilerplate_description, |
| isolate, allocation}, |
| elements); |
| } |
| |
| Handle<JSObject> CreateArrayLiteral( |
| Isolate* isolate, |
| DirectHandle<ArrayBoilerplateDescription> array_boilerplate_description, |
| AllocationType allocation) { |
| ElementsKind constant_elements_kind = |
| array_boilerplate_description->elements_kind(); |
| |
| Handle<FixedArrayBase> constant_elements_values( |
| array_boilerplate_description->constant_elements(), isolate); |
| |
| // Create the JSArray. |
| Handle<FixedArrayBase> copied_elements_values; |
| if (IsDoubleElementsKind(constant_elements_kind)) { |
| copied_elements_values = isolate->factory()->CopyFixedDoubleArray( |
| Cast<FixedDoubleArray>(constant_elements_values)); |
| } else { |
| DCHECK(IsSmiOrObjectElementsKind(constant_elements_kind)); |
| const bool is_cow = (constant_elements_values->map() == |
| ReadOnlyRoots(isolate).fixed_cow_array_map()); |
| if (is_cow) { |
| copied_elements_values = constant_elements_values; |
| if (DEBUG_BOOL) { |
| auto fixed_array_values = Cast<FixedArray>(copied_elements_values); |
| uint32_t fixed_array_values_len = fixed_array_values->ulength().value(); |
| for (uint32_t i = 0; i < fixed_array_values_len; i++) { |
| DCHECK(!IsFixedArray(fixed_array_values->get(i))); |
| } |
| } |
| } else { |
| Handle<FixedArray> fixed_array_values = |
| Cast<FixedArray>(constant_elements_values); |
| Handle<FixedArray> fixed_array_values_copy = |
| isolate->factory()->CopyFixedArray(fixed_array_values); |
| copied_elements_values = fixed_array_values_copy; |
| uint32_t fixed_array_values_len = fixed_array_values->ulength().value(); |
| for (uint32_t i = 0; i < fixed_array_values_len; i++) { |
| Tagged<Object> value = fixed_array_values_copy->get(i); |
| Tagged<HeapObject> value_heap_object; |
| if (!value.GetHeapObject(isolate, &value_heap_object)) continue; |
| if (IsAnyHole(value_heap_object)) continue; |
| |
| if (IsArrayBoilerplateDescription(value_heap_object)) { |
| HandleScope sub_scope(isolate); |
| DirectHandle<ArrayBoilerplateDescription> boilerplate( |
| Cast<ArrayBoilerplateDescription>(value_heap_object), isolate); |
| DirectHandle<JSObject> result = |
| CreateArrayLiteral(isolate, boilerplate, allocation); |
| fixed_array_values_copy->set(i, *result); |
| |
| } else if (IsObjectBoilerplateDescription(value_heap_object)) { |
| HandleScope sub_scope(isolate); |
| DirectHandle<ObjectBoilerplateDescription> boilerplate( |
| Cast<ObjectBoilerplateDescription>(value_heap_object), isolate); |
| DirectHandle<JSObject> result = CreateObjectLiteral( |
| isolate, boilerplate, boilerplate->flags(), allocation); |
| fixed_array_values_copy->set(i, *result); |
| } |
| } |
| } |
| } |
| return isolate->factory()->NewJSArrayWithElements( |
| copied_elements_values, constant_elements_kind, |
| copied_elements_values->ulength().value(), allocation); |
| } |
| |
| template <typename LiteralHelper> |
| MaybeDirectHandle<JSObject> CreateLiteralWithoutAllocationSite( |
| Isolate* isolate, Handle<HeapObject> description, int flags) { |
| return LiteralHelper::Create(isolate, description, flags, |
| AllocationType::kYoung); |
| } |
| |
| template <typename LiteralHelper> |
| MaybeDirectHandle<JSObject> CreateLiteral(Isolate* isolate, |
| Handle<HeapObject> maybe_vector, |
| int literals_index, |
| Handle<HeapObject> description, |
| int flags) { |
| if (!IsFeedbackVector(*maybe_vector)) { |
| DCHECK(IsUndefined(*maybe_vector)); |
| return CreateLiteralWithoutAllocationSite<LiteralHelper>( |
| isolate, description, flags); |
| } |
| auto vector = Cast<FeedbackVector>(maybe_vector); |
| FeedbackSlot literals_slot(FeedbackVector::ToSlot(literals_index)); |
| CHECK_LT(literals_slot.ToInt(), vector->length().value()); |
| Handle<Object> literal_site(Cast<Object>(vector->Get(literals_slot)), |
| isolate); |
| Handle<AllocationSite> site; |
| Handle<JSObject> boilerplate; |
| |
| if (HasBoilerplate(literal_site)) { |
| site = Cast<AllocationSite>(literal_site); |
| boilerplate = Handle<JSObject>(site->boilerplate(), isolate); |
| } else { |
| // Eagerly create AllocationSites for literals that contain an Array. |
| bool needs_initial_allocation_site = |
| (flags & AggregateLiteral::kNeedsInitialAllocationSite) != 0; |
| if (!needs_initial_allocation_site && |
| IsUninitializedLiteralSite(*literal_site)) { |
| PreInitializeLiteralSite(vector, literals_slot); |
| return CreateLiteralWithoutAllocationSite<LiteralHelper>( |
| isolate, description, flags); |
| } else { |
| boilerplate = LiteralHelper::Create(isolate, description, flags, |
| AllocationType::kOld); |
| } |
| // Install AllocationSite objects. |
| AllocationSiteCreationContext creation_context(isolate); |
| site = creation_context.EnterNewScope(); |
| RETURN_ON_EXCEPTION(isolate, DeepWalk(boilerplate, &creation_context)); |
| creation_context.ExitScope(site, boilerplate); |
| |
| vector->SynchronizedSet(literals_slot, *site); |
| } |
| |
| static_assert(static_cast<int>(ObjectLiteral::kDisableMementos) == |
| static_cast<int>(ArrayLiteral::kDisableMementos)); |
| bool enable_mementos = (flags & ObjectLiteral::kDisableMementos) == 0; |
| |
| // Copy the existing boilerplate. |
| AllocationSiteUsageContext usage_context(isolate, site, enable_mementos); |
| usage_context.EnterNewScope(); |
| MaybeDirectHandle<JSObject> copy = DeepCopy(boilerplate, &usage_context); |
| usage_context.ExitScope(site, boilerplate); |
| return copy; |
| } |
| |
| DirectHandle<Object> InstantiateIfSharedFunctionInfo( |
| DirectHandle<Context> context, Isolate* isolate, |
| DirectHandle<JSObject> js_proto, DirectHandle<Object> value, |
| DirectHandle<ClosureFeedbackCellArray> feedback_cell_array, |
| Handle<ObjectBoilerplateDescription> object_boilerplate_description, |
| int start_slot, int& current_slot) { |
| DirectHandle<SharedFunctionInfo> shared; |
| if (!TryCast<SharedFunctionInfo>(value, &shared)) { |
| return value; |
| } |
| |
| if (!v8_flags.proto_assign_seq_lazy_func_opt || |
| !base::IsInRange(current_slot, 0, kMaxUInt16)) { |
| DirectHandle<FeedbackCell> feedback_cell( |
| feedback_cell_array->get(current_slot), isolate); |
| value = Factory::JSFunctionBuilder{isolate, shared, context} |
| .set_feedback_cell(feedback_cell) |
| .set_allocation_type(AllocationType::kYoung) |
| .Build(); |
| ++current_slot; |
| return value; |
| } |
| |
| DirectHandle<Map> proto_map = direct_handle(js_proto->map(), isolate); |
| if (Tagged<PrototypeSharedClosureInfo> closure_info; |
| proto_map->TryGetPrototypeSharedClosureInfo(&closure_info)) { |
| // We already have closure infos on this prototype, this means we |
| // already called SetPrototypeProperties on it and some closures were |
| // set up. We can only take the lazy closure path if the context |
| // is the same. |
| if (closure_info->context() == *context && |
| *object_boilerplate_description == |
| closure_info->boilerplate_description()) { |
| // fast path |
| shared->set_feedback_slot(current_slot); |
| } else { |
| // not lazy allocation |
| DirectHandle<FeedbackCell> feedback_cell( |
| feedback_cell_array->get(current_slot), isolate); |
| value = Factory::JSFunctionBuilder{isolate, shared, context} |
| .set_feedback_cell(feedback_cell) |
| .set_allocation_type(AllocationType::kYoung) |
| .Build(); |
| } |
| } else { |
| // We do not have closure_info |
| auto val = *isolate->factory()->NewPrototypeSharedClosureInfo( |
| object_boilerplate_description, context, feedback_cell_array); |
| |
| proto_map->SetPrototypeSharedClosureInfo(val); |
| shared->set_feedback_slot(current_slot); |
| } |
| ++current_slot; |
| return value; |
| } |
| |
| } // namespace |
| |
| RUNTIME_FUNCTION(Runtime_CreateObjectLiteral) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(4, args.length()); |
| Handle<HeapObject> maybe_vector = args.at<HeapObject>(0); |
| int literals_index = args.tagged_index_value_at(1); |
| Handle<ObjectBoilerplateDescription> description = |
| args.at<ObjectBoilerplateDescription>(2); |
| int flags = args.smi_value_at(3); |
| RETURN_RESULT_OR_FAILURE( |
| isolate, CreateLiteral<ObjectLiteralHelper>( |
| isolate, maybe_vector, literals_index, description, flags)); |
| } |
| |
| static MaybeDirectHandle<Object> SetPrototypePropertiesSlow( |
| Isolate* isolate, DirectHandle<Context> context, DirectHandle<JSAny> obj, |
| Handle<ObjectBoilerplateDescription> object_boilerplate_description, |
| DirectHandle<ClosureFeedbackCellArray> feedback_cell_array, |
| int& current_slot, int start_index = 0) { |
| MaybeDirectHandle<Object> result; |
| |
| int length = object_boilerplate_description->boilerplate_properties_count(); |
| for (int index = start_index; index < length; index++) { |
| DirectHandle<Object> proto; |
| ASSIGN_RETURN_ON_EXCEPTION( |
| isolate, proto, |
| Runtime::GetObjectProperty(isolate, obj, |
| isolate->factory()->prototype_string())); |
| |
| DirectHandle<Object> key(object_boilerplate_description->name(index), |
| isolate); |
| DirectHandle<Object> value(object_boilerplate_description->value(index), |
| isolate); |
| |
| if (DirectHandle<SharedFunctionInfo> shared; |
| TryCast<SharedFunctionInfo>(value, &shared)) { |
| DirectHandle<FeedbackCell> feedback_cell( |
| feedback_cell_array->get(current_slot++), isolate); |
| value = Factory::JSFunctionBuilder{isolate, shared, context} |
| .set_feedback_cell(feedback_cell) |
| .set_allocation_type(AllocationType::kYoung) |
| .Build(); |
| } |
| |
| RETURN_ON_EXCEPTION( |
| isolate, Runtime::SetObjectProperty(isolate, Cast<JSAny>(proto), key, |
| value, StoreOrigin::kNamed)); |
| |
| result = value; |
| } |
| |
| return result.ToHandleChecked(); |
| } |
| |
| static bool IsDefaultFunctionPrototype(DirectHandle<JSObject> js_proto, |
| Isolate* isolate) { |
| // Object function prototype's map. |
| Tagged<Map> proto_map = js_proto->map(); |
| |
| // Check that given function.prototype object has a default initial state: |
| // it's extensible. |
| if (!proto_map->is_extensible()) { |
| return false; |
| } |
| |
| // it's in dictionary mode. |
| if (!proto_map->is_dictionary_map()) { |
| return false; |
| } |
| |
| // it has exactly one "constructor" property installed. |
| if (js_proto->property_dictionary()->NumberOfElements() != 1) { |
| return false; |
| } |
| if (js_proto->property_dictionary() |
| ->FindEntry(isolate, isolate->factory()->constructor_string()) |
| .is_not_found()) { |
| return false; |
| } |
| |
| // its prototype is the original and unmodified Object.prototype object. |
| if (proto_map->prototype()->map() != |
| *isolate->object_function_prototype_map()) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| RUNTIME_FUNCTION(Runtime_SetPrototypeProperties) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(4, args.length()); |
| DirectHandle<Context> context(isolate->context(), isolate); |
| DirectHandle<JSAny> obj = args.at<JSAny>(0); // acc JS Object |
| Handle<ObjectBoilerplateDescription> object_boilerplate_description = |
| args.at<ObjectBoilerplateDescription>(1); |
| DirectHandle<ClosureFeedbackCellArray> feedback_cell_array = |
| args.at<ClosureFeedbackCellArray>(2); |
| int current_slot = args.smi_value_at(3); |
| int start_slot = current_slot; |
| |
| // Proxy and any non-function not welcome |
| if (!IsJSFunction(*obj)) { |
| RETURN_RESULT_OR_FAILURE( |
| isolate, SetPrototypePropertiesSlow(isolate, context, obj, |
| object_boilerplate_description, |
| feedback_cell_array, current_slot)); |
| } |
| |
| DirectHandle<JSFunction> acc_fun = Cast<JSFunction>(obj); |
| if (!acc_fun->has_prototype_slot()) { |
| RETURN_RESULT_OR_FAILURE( |
| isolate, SetPrototypePropertiesSlow(isolate, context, obj, |
| object_boilerplate_description, |
| feedback_cell_array, current_slot)); |
| } |
| |
| DirectHandle<Object> prototype = |
| JSFunction::GetFunctionPrototype(isolate, acc_fun); |
| |
| DCHECK_EQ(*prototype, |
| *Runtime::GetObjectProperty(isolate, obj, |
| isolate->factory()->prototype_string()) |
| .ToHandleChecked()); |
| |
| if (IsNull(*prototype)) { |
| RETURN_RESULT_OR_FAILURE( |
| isolate, SetPrototypePropertiesSlow(isolate, context, obj, |
| object_boilerplate_description, |
| feedback_cell_array, current_slot)); |
| } |
| |
| DirectHandle<JSObject> js_proto; |
| if (!TryCast<JSObject>(prototype, &js_proto)) { |
| RETURN_RESULT_OR_FAILURE( |
| isolate, SetPrototypePropertiesSlow(isolate, context, obj, |
| object_boilerplate_description, |
| feedback_cell_array, current_slot)); |
| } |
| |
| if (IsSpecialReceiverMap(js_proto->map())) { |
| RETURN_RESULT_OR_FAILURE( |
| isolate, SetPrototypePropertiesSlow(isolate, context, obj, |
| object_boilerplate_description, |
| feedback_cell_array, current_slot)); |
| } |
| |
| if (!JSObject::IsExtensible(isolate, js_proto)) { |
| RETURN_RESULT_OR_FAILURE( |
| isolate, SetPrototypePropertiesSlow(isolate, context, obj, |
| object_boilerplate_description, |
| feedback_cell_array, current_slot)); |
| } |
| |
| bool is_default_func_prototype = |
| IsDefaultFunctionPrototype(js_proto, isolate); |
| |
| // It should now be safe to perform a fast merge |
| MaybeDirectHandle<Object> result; |
| int length = object_boilerplate_description->boilerplate_properties_count(); |
| if (is_default_func_prototype) { |
| for (int index = 0; index < length; index++) { |
| DirectHandle<Object> key(object_boilerplate_description->name(index), |
| isolate); |
| DirectHandle<Object> value(object_boilerplate_description->value(index), |
| isolate); |
| |
| value = InstantiateIfSharedFunctionInfo( |
| context, isolate, js_proto, value, feedback_cell_array, |
| object_boilerplate_description, start_slot, current_slot); |
| |
| DirectHandle<String> name = Cast<String>(key); |
| DCHECK(!name->IsArrayIndex()); |
| DCHECK(!IsTheHole(*value)); |
| LookupIterator it(isolate, js_proto, name, LookupIterator::OWN); |
| |
| if (IsSharedFunctionInfo(*value)) { |
| DirectHandle<AccessorInfo> accessor_info = |
| isolate->factory()->lazy_closure_accessor(); |
| |
| JSObject::SetAccessor(js_proto, name, accessor_info, |
| PropertyAttributes::NONE) |
| .Check(); |
| } else { |
| Object::TransitionAndWriteDataProperty( |
| &it, value, NONE, Just(kDontThrow), StoreOrigin::kNamed) |
| .Check(); |
| } |
| result = value; |
| } |
| } else { |
| // Make sure None of the keys we are writing to are setters/getters |
| // TODO(rherouart): if prototype is empty we can skip these checks |
| for (int index = 0; index < length; index++) { |
| PropertyDescriptor desc; |
| DirectHandle<Object> key(object_boilerplate_description->name(index), |
| isolate); |
| DirectHandle<Object> value(object_boilerplate_description->value(index), |
| isolate); |
| |
| CHECK(IsName(*key)); |
| PropertyKey lookup_key(isolate, key); |
| |
| LookupIterator it(isolate, js_proto, lookup_key, |
| LookupIterator::PROTOTYPE_CHAIN); |
| |
| LookupIterator::State it_state = it.state(); |
| if (it_state != LookupIterator::NOT_FOUND && |
| (it_state != LookupIterator::DATA || it.IsReadOnly())) { |
| RETURN_RESULT_OR_FAILURE( |
| isolate, SetPrototypePropertiesSlow( |
| isolate, context, obj, object_boilerplate_description, |
| feedback_cell_array, current_slot, index)); |
| } |
| DCHECK(!IsTheHole(*value)); |
| |
| if (it_state == LookupIterator::DATA && |
| it.HolderIsReceiverOrHiddenPrototype()) { |
| DirectHandle<SharedFunctionInfo> shared; |
| if (TryCast<SharedFunctionInfo>(value, &shared)) { |
| // If we were to set an existing property to a SharedFunctionInfo, |
| // there would be the risk of it being returned from IC without being |
| // instantiated. |
| DirectHandle<FeedbackCell> feedback_cell( |
| feedback_cell_array->get(current_slot), isolate); |
| value = Factory::JSFunctionBuilder{isolate, shared, context} |
| .set_feedback_cell(feedback_cell) |
| .set_allocation_type(AllocationType::kYoung) |
| .Build(); |
| current_slot++; |
| } |
| it.UpdateProtector(); |
| Object::SetDataProperty(&it, value).Check(); |
| } else { |
| value = InstantiateIfSharedFunctionInfo( |
| context, isolate, js_proto, value, feedback_cell_array, |
| object_boilerplate_description, start_slot, current_slot); |
| if (IsSharedFunctionInfo(*value)) { |
| DirectHandle<AccessorInfo> accessor_info = |
| isolate->factory()->lazy_closure_accessor(); |
| |
| // Unlike SetDataProperty/TransitionAndWriteDataProperty, SetAccessor |
| // doesn't invalidate protectors, so do it here for e.g. `then` or |
| // `next` added to a watched prototype. |
| it.UpdateProtector(); |
| JSObject::SetAccessor(js_proto, Cast<Name>(key), accessor_info, |
| PropertyAttributes::NONE) |
| .Check(); |
| } else { |
| Object::TransitionAndWriteDataProperty( |
| &it, value, NONE, Just(kDontThrow), StoreOrigin::kNamed) |
| .Check(); |
| } |
| } |
| result = value; |
| } |
| } |
| |
| return *result.ToHandleChecked(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_CreateArrayLiteral) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(4, args.length()); |
| Handle<HeapObject> maybe_vector = args.at<HeapObject>(0); |
| int literals_index = args.tagged_index_value_at(1); |
| Handle<ArrayBoilerplateDescription> elements = |
| args.at<ArrayBoilerplateDescription>(2); |
| int flags = args.smi_value_at(3); |
| RETURN_RESULT_OR_FAILURE( |
| isolate, CreateLiteral<ArrayLiteralHelper>( |
| isolate, maybe_vector, literals_index, elements, flags)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_CreateRegExpLiteral) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(4, args.length()); |
| Handle<HeapObject> maybe_vector = args.at<HeapObject>(0); |
| int index = args.tagged_index_value_at(1); |
| DirectHandle<String> pattern = args.at<String>(2); |
| int flags = args.smi_value_at(3); |
| |
| if (IsUndefined(*maybe_vector)) { |
| // We don't have a vector; don't create a boilerplate, simply construct a |
| // plain JSRegExp instance and return it. |
| RETURN_RESULT_OR_FAILURE( |
| isolate, JSRegExp::New(isolate, pattern, JSRegExp::Flags(flags))); |
| } |
| |
| auto vector = Cast<FeedbackVector>(maybe_vector); |
| FeedbackSlot literal_slot(FeedbackVector::ToSlot(index)); |
| DirectHandle<Object> literal_site(Cast<Object>(vector->Get(literal_slot)), |
| isolate); |
| |
| // This function must not be called when a boilerplate already exists (if it |
| // exists, callers should instead copy the boilerplate into a new JSRegExp |
| // instance). |
| CHECK(!HasBoilerplate(literal_site)); |
| |
| DirectHandle<JSRegExp> regexp_instance; |
| ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| isolate, regexp_instance, |
| JSRegExp::New(isolate, pattern, JSRegExp::Flags(flags))); |
| |
| // JSRegExp literal sites are initialized in a two-step process: |
| // Uninitialized-Preinitialized, and Preinitialized-Initialized. |
| if (IsUninitializedLiteralSite(*literal_site)) { |
| PreInitializeLiteralSite(vector, literal_slot); |
| return *regexp_instance; |
| } |
| |
| DirectHandle<RegExpData> data(regexp_instance->data(isolate), isolate); |
| DirectHandle<RegExpBoilerplateDescription> boilerplate = |
| isolate->factory()->NewRegExpBoilerplateDescription( |
| data, Smi::FromInt(static_cast<int>(regexp_instance->flags()))); |
| |
| vector->SynchronizedSet(literal_slot, *boilerplate); |
| DCHECK(HasBoilerplate( |
| direct_handle(Cast<Object>(vector->Get(literal_slot)), isolate))); |
| |
| return *regexp_instance; |
| } |
| |
| } // namespace internal |
| } // namespace v8 |