// 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/lookup.h" #include "src/bootstrapper.h" #include "src/deoptimizer.h" #include "src/elements.h" #include "src/field-type.h" #include "src/isolate-inl.h" #include "src/objects/hash-table-inl.h" namespace v8 { namespace internal { // static LookupIterator LookupIterator::PropertyOrElement( Isolate* isolate, Handle receiver, Handle key, bool* success, Handle holder, Configuration configuration) { uint32_t index = 0; if (key->ToArrayIndex(&index)) { *success = true; return LookupIterator(isolate, receiver, index, holder, configuration); } Handle name; *success = Object::ToName(isolate, key).ToHandle(&name); if (!*success) { DCHECK(isolate->has_pending_exception()); // Return an unusable dummy. return LookupIterator(isolate, receiver, isolate->factory()->empty_string()); } if (name->AsArrayIndex(&index)) { LookupIterator it(isolate, receiver, index, holder, configuration); // Here we try to avoid having to rebuild the string later // by storing it on the indexed LookupIterator. it.name_ = name; return it; } return LookupIterator(receiver, name, holder, configuration); } // static LookupIterator LookupIterator::PropertyOrElement(Isolate* isolate, Handle receiver, Handle key, bool* success, Configuration configuration) { // TODO(mslekova): come up with better way to avoid duplication uint32_t index = 0; if (key->ToArrayIndex(&index)) { *success = true; return LookupIterator(isolate, receiver, index, configuration); } Handle name; *success = Object::ToName(isolate, key).ToHandle(&name); if (!*success) { DCHECK(isolate->has_pending_exception()); // Return an unusable dummy. return LookupIterator(isolate, receiver, isolate->factory()->empty_string()); } if (name->AsArrayIndex(&index)) { LookupIterator it(isolate, receiver, index, configuration); // Here we try to avoid having to rebuild the string later // by storing it on the indexed LookupIterator. it.name_ = name; return it; } return LookupIterator(isolate, receiver, name, configuration); } // TODO(ishell): Consider removing this way of LookupIterator creation. // static LookupIterator LookupIterator::ForTransitionHandler( Isolate* isolate, Handle receiver, Handle name, Handle value, MaybeHandle maybe_transition_map) { Handle transition_map; if (!maybe_transition_map.ToHandle(&transition_map) || !transition_map->IsPrototypeValidityCellValid()) { // This map is not a valid transition handler, so full lookup is required. return LookupIterator(isolate, receiver, name); } PropertyDetails details = PropertyDetails::Empty(); bool has_property; if (transition_map->is_dictionary_map()) { details = PropertyDetails(kData, NONE, PropertyCellType::kNoCell); has_property = false; } else { details = transition_map->GetLastDescriptorDetails(); has_property = true; } #ifdef DEBUG if (name->IsPrivate()) { DCHECK_EQ(DONT_ENUM, details.attributes()); } else { DCHECK_EQ(NONE, details.attributes()); } #endif LookupIterator it(isolate, receiver, name, transition_map, details, has_property); if (!transition_map->is_dictionary_map()) { int descriptor_number = transition_map->LastAdded(); Handle new_map = Map::PrepareForDataProperty(isolate, transition_map, descriptor_number, PropertyConstness::kConst, value); // Reload information; this is no-op if nothing changed. it.property_details_ = new_map->instance_descriptors()->GetDetails(descriptor_number); it.transition_ = new_map; } return it; } LookupIterator::LookupIterator(Isolate* isolate, Handle receiver, Handle name, Handle transition_map, PropertyDetails details, bool has_property) : configuration_(DEFAULT), state_(TRANSITION), has_property_(has_property), interceptor_state_(InterceptorState::kUninitialized), property_details_(details), isolate_(isolate), name_(name), transition_(transition_map), receiver_(receiver), initial_holder_(GetRoot(isolate, receiver)), index_(kMaxUInt32), number_(static_cast(DescriptorArray::kNotFound)) { holder_ = initial_holder_; } template void LookupIterator::Start() { DisallowHeapAllocation no_gc; has_property_ = false; state_ = NOT_FOUND; holder_ = initial_holder_; JSReceiver* holder = *holder_; Map* map = holder->map(); state_ = LookupInHolder(map, holder); if (IsFound()) return; NextInternal(map, holder); } template void LookupIterator::Start(); template void LookupIterator::Start(); void LookupIterator::Next() { DCHECK_NE(JSPROXY, state_); DCHECK_NE(TRANSITION, state_); DisallowHeapAllocation no_gc; has_property_ = false; JSReceiver* holder = *holder_; Map* map = holder->map(); if (map->IsSpecialReceiverMap()) { state_ = IsElement() ? LookupInSpecialHolder(map, holder) : LookupInSpecialHolder(map, holder); if (IsFound()) return; } IsElement() ? NextInternal(map, holder) : NextInternal(map, holder); } template void LookupIterator::NextInternal(Map* map, JSReceiver* holder) { do { JSReceiver* maybe_holder = NextHolder(map); if (maybe_holder == nullptr) { if (interceptor_state_ == InterceptorState::kSkipNonMasking) { RestartLookupForNonMaskingInterceptors(); return; } state_ = NOT_FOUND; if (holder != *holder_) holder_ = handle(holder, isolate_); return; } holder = maybe_holder; map = holder->map(); state_ = LookupInHolder(map, holder); } while (!IsFound()); holder_ = handle(holder, isolate_); } template void LookupIterator::RestartInternal(InterceptorState interceptor_state) { interceptor_state_ = interceptor_state; property_details_ = PropertyDetails::Empty(); number_ = static_cast(DescriptorArray::kNotFound); Start(); } template void LookupIterator::RestartInternal(InterceptorState); template void LookupIterator::RestartInternal(InterceptorState); // static Handle LookupIterator::GetRootForNonJSReceiver( Isolate* isolate, Handle receiver, uint32_t index) { // Strings are the only objects with properties (only elements) directly on // the wrapper. Hence we can skip generating the wrapper for all other cases. if (index != kMaxUInt32 && receiver->IsString() && index < static_cast(String::cast(*receiver)->length())) { // TODO(verwaest): Speed this up. Perhaps use a cached wrapper on the native // context, ensuring that we don't leak it into JS? Handle constructor = isolate->string_function(); Handle result = isolate->factory()->NewJSObject(constructor); Handle::cast(result)->set_value(*receiver); return result; } auto root = handle(receiver->GetPrototypeChainRootMap(isolate)->prototype(), isolate); if (root->IsNull(isolate)) { isolate->PushStackTraceAndDie(*receiver); } return Handle::cast(root); } Handle LookupIterator::GetReceiverMap() const { if (receiver_->IsNumber()) return factory()->heap_number_map(); return handle(Handle::cast(receiver_)->map(), isolate_); } bool LookupIterator::HasAccess() const { DCHECK_EQ(ACCESS_CHECK, state_); return isolate_->MayAccess(handle(isolate_->context(), isolate_), GetHolder()); } template void LookupIterator::ReloadPropertyInformation() { state_ = BEFORE_PROPERTY; interceptor_state_ = InterceptorState::kUninitialized; state_ = LookupInHolder(holder_->map(), *holder_); DCHECK(IsFound() || !holder_->HasFastProperties()); } namespace { bool IsTypedArrayFunctionInAnyContext(Isolate* isolate, JSReceiver* holder) { static uint32_t context_slots[] = { #define TYPED_ARRAY_CONTEXT_SLOTS(Type, type, TYPE, ctype) \ Context::TYPE##_ARRAY_FUN_INDEX, TYPED_ARRAYS(TYPED_ARRAY_CONTEXT_SLOTS) #undef TYPED_ARRAY_CONTEXT_SLOTS }; if (!holder->IsJSFunction()) return false; return std::any_of( std::begin(context_slots), std::end(context_slots), [=](uint32_t slot) { return isolate->IsInAnyContext(holder, slot); }); } } // namespace void LookupIterator::InternalUpdateProtector() { if (isolate_->bootstrapper()->IsActive()) return; ReadOnlyRoots roots(heap()); if (*name_ == roots.constructor_string()) { if (!isolate_->IsArraySpeciesLookupChainIntact() && !isolate_->IsTypedArraySpeciesLookupChainIntact() && !isolate_->IsPromiseSpeciesLookupChainIntact()) return; // Setting the constructor property could change an instance's @@species if (holder_->IsJSArray()) { if (!isolate_->IsArraySpeciesLookupChainIntact()) return; isolate_->CountUsage( v8::Isolate::UseCounterFeature::kArrayInstanceConstructorModified); isolate_->InvalidateArraySpeciesProtector(); return; } else if (holder_->IsJSPromise()) { if (!isolate_->IsPromiseSpeciesLookupChainIntact()) return; isolate_->InvalidatePromiseSpeciesProtector(); return; } else if (holder_->IsJSTypedArray()) { if (!isolate_->IsTypedArraySpeciesLookupChainIntact()) return; isolate_->InvalidateTypedArraySpeciesProtector(); return; } if (holder_->map()->is_prototype_map()) { DisallowHeapAllocation no_gc; // Setting the constructor of Array.prototype, Promise.prototype or // %TypedArray%.prototype of any realm also needs to invalidate the // @@species protector. // For typed arrays, we check a prototype of this holder since TypedArrays // have different prototypes for each type, and their parent prototype is // pointing the same TYPED_ARRAY_PROTOTYPE. if (isolate_->IsInAnyContext(*holder_, Context::INITIAL_ARRAY_PROTOTYPE_INDEX)) { if (!isolate_->IsArraySpeciesLookupChainIntact()) return; isolate_->CountUsage( v8::Isolate::UseCounterFeature::kArrayPrototypeConstructorModified); isolate_->InvalidateArraySpeciesProtector(); } else if (isolate_->IsInAnyContext(*holder_, Context::PROMISE_PROTOTYPE_INDEX)) { if (!isolate_->IsPromiseSpeciesLookupChainIntact()) return; isolate_->InvalidatePromiseSpeciesProtector(); } else if (isolate_->IsInAnyContext( holder_->map()->prototype(), Context::TYPED_ARRAY_PROTOTYPE_INDEX)) { if (!isolate_->IsTypedArraySpeciesLookupChainIntact()) return; isolate_->InvalidateTypedArraySpeciesProtector(); } } } else if (*name_ == roots.next_string()) { if (isolate_->IsInAnyContext( *holder_, Context::INITIAL_ARRAY_ITERATOR_PROTOTYPE_INDEX)) { // Setting the next property of %ArrayIteratorPrototype% also needs to // invalidate the array iterator protector. if (!isolate_->IsArrayIteratorLookupChainIntact()) return; isolate_->InvalidateArrayIteratorProtector(); } else if (isolate_->IsInAnyContext( *receiver_, Context::INITIAL_STRING_ITERATOR_PROTOTYPE_INDEX)) { // Setting the next property of %StringIteratorPrototype% invalidates the // string iterator protector. if (!isolate_->IsStringIteratorLookupChainIntact()) return; isolate_->InvalidateStringIteratorProtector(); } } else if (*name_ == roots.species_symbol()) { if (!isolate_->IsArraySpeciesLookupChainIntact() && !isolate_->IsTypedArraySpeciesLookupChainIntact() && !isolate_->IsPromiseSpeciesLookupChainIntact()) return; // Setting the Symbol.species property of any Array, Promise or TypedArray // constructor invalidates the @@species protector if (isolate_->IsInAnyContext(*holder_, Context::ARRAY_FUNCTION_INDEX)) { if (!isolate_->IsArraySpeciesLookupChainIntact()) return; isolate_->CountUsage( v8::Isolate::UseCounterFeature::kArraySpeciesModified); isolate_->InvalidateArraySpeciesProtector(); } else if (isolate_->IsInAnyContext(*holder_, Context::PROMISE_FUNCTION_INDEX)) { if (!isolate_->IsPromiseSpeciesLookupChainIntact()) return; isolate_->InvalidatePromiseSpeciesProtector(); } else if (IsTypedArrayFunctionInAnyContext(isolate_, *holder_)) { if (!isolate_->IsTypedArraySpeciesLookupChainIntact()) return; isolate_->InvalidateTypedArraySpeciesProtector(); } } else if (*name_ == roots.is_concat_spreadable_symbol()) { if (!isolate_->IsIsConcatSpreadableLookupChainIntact()) return; isolate_->InvalidateIsConcatSpreadableProtector(); } else if (*name_ == roots.iterator_symbol()) { if (holder_->IsJSArray()) { if (!isolate_->IsArrayIteratorLookupChainIntact()) return; isolate_->InvalidateArrayIteratorProtector(); } else if (isolate_->IsInAnyContext( *receiver_, Context::INITIAL_STRING_PROTOTYPE_INDEX)) { // Setting the Symbol.iterator property of String.prototype invalidates // the string iterator protector. Symbol.iterator can also be set on a // String wrapper, but not on a primitive string. We only support // protector for primitive strings. if (!isolate_->IsStringIteratorLookupChainIntact()) return; isolate_->InvalidateStringIteratorProtector(); } } else if (*name_ == roots.resolve_string()) { if (!isolate_->IsPromiseResolveLookupChainIntact()) return; // Setting the "resolve" property on any %Promise% intrinsic object // invalidates the Promise.resolve protector. if (isolate_->IsInAnyContext(*holder_, Context::PROMISE_FUNCTION_INDEX)) { isolate_->InvalidatePromiseResolveProtector(); } } else if (*name_ == roots.then_string()) { if (!isolate_->IsPromiseThenLookupChainIntact()) return; // Setting the "then" property on any JSPromise instance or on the // initial %PromisePrototype% invalidates the Promise#then protector. // Also setting the "then" property on the initial %ObjectPrototype% // invalidates the Promise#then protector, since we use this protector // to guard the fast-path in AsyncGeneratorResolve, where we can skip // the ResolvePromise step and go directly to FulfillPromise if we // know that the Object.prototype doesn't contain a "then" method. if (holder_->IsJSPromise() || isolate_->IsInAnyContext(*holder_, Context::INITIAL_OBJECT_PROTOTYPE_INDEX) || isolate_->IsInAnyContext(*holder_, Context::PROMISE_PROTOTYPE_INDEX)) { isolate_->InvalidatePromiseThenProtector(); } } } void LookupIterator::PrepareForDataProperty(Handle value) { DCHECK(state_ == DATA || state_ == ACCESSOR); DCHECK(HolderIsReceiverOrHiddenPrototype()); Handle holder = GetHolder(); // JSProxy does not have fast properties so we do an early return. DCHECK_IMPLIES(holder->IsJSProxy(), !holder->HasFastProperties()); DCHECK_IMPLIES(holder->IsJSProxy(), name()->IsPrivate()); if (holder->IsJSProxy()) return; Handle holder_obj = Handle::cast(holder); if (IsElement()) { ElementsKind kind = holder_obj->GetElementsKind(); ElementsKind to = value->OptimalElementsKind(); if (IsHoleyElementsKind(kind)) to = GetHoleyElementsKind(to); to = GetMoreGeneralElementsKind(kind, to); if (kind != to) { JSObject::TransitionElementsKind(holder_obj, to); } // Copy the backing store if it is copy-on-write. if (IsSmiOrObjectElementsKind(to)) { JSObject::EnsureWritableFastElements(holder_obj); } return; } if (holder_obj->IsJSGlobalObject()) { Handle dictionary( JSGlobalObject::cast(*holder_obj)->global_dictionary(), isolate()); Handle cell(dictionary->CellAt(dictionary_entry()), isolate()); property_details_ = cell->property_details(); PropertyCell::PrepareForValue(isolate(), dictionary, dictionary_entry(), value, property_details_); return; } if (!holder_obj->HasFastProperties()) return; PropertyConstness new_constness = PropertyConstness::kConst; if (FLAG_track_constant_fields) { if (constness() == PropertyConstness::kConst) { DCHECK_EQ(kData, property_details_.kind()); // Check that current value matches new value otherwise we should make // the property mutable. if (!IsConstFieldValueEqualTo(*value)) new_constness = PropertyConstness::kMutable; } } else { new_constness = PropertyConstness::kMutable; } Handle old_map(holder_obj->map(), isolate_); Handle new_map = Map::PrepareForDataProperty( isolate(), old_map, descriptor_number(), new_constness, value); if (old_map.is_identical_to(new_map)) { // Update the property details if the representation was None. if (constness() != new_constness || representation().IsNone()) { property_details_ = new_map->instance_descriptors()->GetDetails(descriptor_number()); } return; } JSObject::MigrateToMap(holder_obj, new_map); ReloadPropertyInformation(); } void LookupIterator::ReconfigureDataProperty(Handle value, PropertyAttributes attributes) { DCHECK(state_ == DATA || state_ == ACCESSOR); DCHECK(HolderIsReceiverOrHiddenPrototype()); Handle holder = GetHolder(); // Property details can never change for private fields. if (holder->IsJSProxy()) { DCHECK(name()->IsPrivate()); return; } Handle holder_obj = Handle::cast(holder); if (IsElement()) { DCHECK(!holder_obj->HasFixedTypedArrayElements()); DCHECK(attributes != NONE || !holder_obj->HasFastElements()); Handle elements(holder_obj->elements(), isolate()); holder_obj->GetElementsAccessor()->Reconfigure(holder_obj, elements, number_, value, attributes); ReloadPropertyInformation(); } else if (holder_obj->HasFastProperties()) { Handle old_map(holder_obj->map(), isolate_); Handle new_map = Map::ReconfigureExistingProperty( isolate_, old_map, descriptor_number(), i::kData, attributes); // Force mutable to avoid changing constant value by reconfiguring // kData -> kAccessor -> kData. new_map = Map::PrepareForDataProperty(isolate(), new_map, descriptor_number(), PropertyConstness::kMutable, value); JSObject::MigrateToMap(holder_obj, new_map); ReloadPropertyInformation(); } if (!IsElement() && !holder_obj->HasFastProperties()) { PropertyDetails details(kData, attributes, PropertyCellType::kMutable); if (holder_obj->map()->is_prototype_map() && (property_details_.attributes() & READ_ONLY) == 0 && (attributes & READ_ONLY) != 0) { // Invalidate prototype validity cell when a property is reconfigured // from writable to read-only as this may invalidate transitioning store // IC handlers. JSObject::InvalidatePrototypeChains(holder->map()); } if (holder_obj->IsJSGlobalObject()) { Handle dictionary( JSGlobalObject::cast(*holder_obj)->global_dictionary(), isolate()); Handle cell = PropertyCell::PrepareForValue( isolate(), dictionary, dictionary_entry(), value, details); cell->set_value(*value); property_details_ = cell->property_details(); } else { Handle dictionary(holder_obj->property_dictionary(), isolate()); PropertyDetails original_details = dictionary->DetailsAt(dictionary_entry()); int enumeration_index = original_details.dictionary_index(); DCHECK_GT(enumeration_index, 0); details = details.set_index(enumeration_index); dictionary->SetEntry(isolate(), dictionary_entry(), *name(), *value, details); property_details_ = details; } state_ = DATA; } WriteDataValue(value, true); #if VERIFY_HEAP if (FLAG_verify_heap) { holder->HeapObjectVerify(isolate()); } #endif } // Can only be called when the receiver is a JSObject. JSProxy has to be handled // via a trap. Adding properties to primitive values is not observable. void LookupIterator::PrepareTransitionToDataProperty( Handle receiver, Handle value, PropertyAttributes attributes, StoreOrigin store_origin) { DCHECK_IMPLIES(receiver->IsJSProxy(), name()->IsPrivate()); DCHECK(receiver.is_identical_to(GetStoreTarget())); if (state_ == TRANSITION) return; if (!IsElement() && name()->IsPrivate()) { attributes = static_cast(attributes | DONT_ENUM); } DCHECK(state_ != LookupIterator::ACCESSOR || (GetAccessors()->IsAccessorInfo() && AccessorInfo::cast(*GetAccessors())->is_special_data_property())); DCHECK_NE(INTEGER_INDEXED_EXOTIC, state_); DCHECK(state_ == NOT_FOUND || !HolderIsReceiverOrHiddenPrototype()); Handle map(receiver->map(), isolate_); // Dictionary maps can always have additional data properties. if (map->is_dictionary_map()) { state_ = TRANSITION; if (map->IsJSGlobalObjectMap()) { // Install a property cell. Handle global = Handle::cast(receiver); int entry; Handle cell = JSGlobalObject::EnsureEmptyPropertyCell( global, name(), PropertyCellType::kUninitialized, &entry); Handle dictionary(global->global_dictionary(), isolate_); DCHECK(cell->value()->IsTheHole(isolate_)); DCHECK(!value->IsTheHole(isolate_)); transition_ = cell; // Assign an enumeration index to the property and update // SetNextEnumerationIndex. int index = dictionary->NextEnumerationIndex(); dictionary->SetNextEnumerationIndex(index + 1); property_details_ = PropertyDetails( kData, attributes, PropertyCellType::kUninitialized, index); PropertyCellType new_type = PropertyCell::UpdatedType(isolate(), cell, value, property_details_); property_details_ = property_details_.set_cell_type(new_type); cell->set_property_details(property_details_); number_ = entry; has_property_ = true; } else { // Don't set enumeration index (it will be set during value store). property_details_ = PropertyDetails(kData, attributes, PropertyCellType::kNoCell); transition_ = map; } return; } Handle transition = Map::TransitionToDataProperty(isolate_, map, name_, value, attributes, kDefaultFieldConstness, store_origin); state_ = TRANSITION; transition_ = transition; if (transition->is_dictionary_map()) { // Don't set enumeration index (it will be set during value store). property_details_ = PropertyDetails(kData, attributes, PropertyCellType::kNoCell); } else { property_details_ = transition->GetLastDescriptorDetails(); has_property_ = true; } } void LookupIterator::ApplyTransitionToDataProperty( Handle receiver) { DCHECK_EQ(TRANSITION, state_); DCHECK(receiver.is_identical_to(GetStoreTarget())); holder_ = receiver; if (receiver->IsJSGlobalObject()) { JSObject::InvalidatePrototypeChains(receiver->map()); state_ = DATA; return; } Handle transition = transition_map(); bool simple_transition = transition->GetBackPointer() == receiver->map(); if (configuration_ == DEFAULT && !transition->is_dictionary_map() && !transition->IsPrototypeValidityCellValid()) { // Only LookupIterator instances with DEFAULT (full prototype chain) // configuration can produce valid transition handler maps. Handle validity_cell = Map::GetOrCreatePrototypeChainValidityCell(transition, isolate()); transition->set_prototype_validity_cell(*validity_cell); } if (!receiver->IsJSProxy()) { JSObject::MigrateToMap(Handle::cast(receiver), transition); } if (simple_transition) { int number = transition->LastAdded(); number_ = static_cast(number); property_details_ = transition->GetLastDescriptorDetails(); state_ = DATA; } else if (receiver->map()->is_dictionary_map()) { Handle dictionary(receiver->property_dictionary(), isolate_); int entry; if (receiver->map()->is_prototype_map() && receiver->IsJSObject()) { JSObject::InvalidatePrototypeChains(receiver->map()); } dictionary = NameDictionary::Add(isolate(), dictionary, name(), isolate_->factory()->uninitialized_value(), property_details_, &entry); receiver->SetProperties(*dictionary); // Reload details containing proper enumeration index value. property_details_ = dictionary->DetailsAt(entry); number_ = entry; has_property_ = true; state_ = DATA; } else { ReloadPropertyInformation(); } } void LookupIterator::Delete() { Handle holder = Handle::cast(holder_); if (IsElement()) { Handle object = Handle::cast(holder); ElementsAccessor* accessor = object->GetElementsAccessor(); accessor->Delete(object, number_); } else { DCHECK(!name()->IsPrivateField()); bool is_prototype_map = holder->map()->is_prototype_map(); RuntimeCallTimerScope stats_scope( isolate_, is_prototype_map ? RuntimeCallCounterId::kPrototypeObject_DeleteProperty : RuntimeCallCounterId::kObject_DeleteProperty); PropertyNormalizationMode mode = is_prototype_map ? KEEP_INOBJECT_PROPERTIES : CLEAR_INOBJECT_PROPERTIES; if (holder->HasFastProperties()) { JSObject::NormalizeProperties(Handle::cast(holder), mode, 0, "DeletingProperty"); ReloadPropertyInformation(); } JSReceiver::DeleteNormalizedProperty(holder, number_); if (holder->IsJSObject()) { JSObject::ReoptimizeIfPrototype(Handle::cast(holder)); } } state_ = NOT_FOUND; } void LookupIterator::TransitionToAccessorProperty( Handle getter, Handle setter, PropertyAttributes attributes) { DCHECK(!getter->IsNull(isolate_) || !setter->IsNull(isolate_)); // Can only be called when the receiver is a JSObject. JSProxy has to be // handled via a trap. Adding properties to primitive values is not // observable. Handle receiver = GetStoreTarget(); if (!IsElement() && name()->IsPrivate()) { attributes = static_cast(attributes | DONT_ENUM); } if (!IsElement() && !receiver->map()->is_dictionary_map()) { Handle old_map(receiver->map(), isolate_); if (!holder_.is_identical_to(receiver)) { holder_ = receiver; state_ = NOT_FOUND; } else if (state_ == INTERCEPTOR) { LookupInRegularHolder(*old_map, *holder_); } int descriptor = IsFound() ? static_cast(number_) : DescriptorArray::kNotFound; Handle new_map = Map::TransitionToAccessorProperty( isolate_, old_map, name_, descriptor, getter, setter, attributes); bool simple_transition = new_map->GetBackPointer() == receiver->map(); JSObject::MigrateToMap(receiver, new_map); if (simple_transition) { int number = new_map->LastAdded(); number_ = static_cast(number); property_details_ = new_map->GetLastDescriptorDetails(); state_ = ACCESSOR; return; } ReloadPropertyInformation(); if (!new_map->is_dictionary_map()) return; } Handle pair; if (state() == ACCESSOR && GetAccessors()->IsAccessorPair()) { pair = Handle::cast(GetAccessors()); // If the component and attributes are identical, nothing has to be done. if (pair->Equals(*getter, *setter)) { if (property_details().attributes() == attributes) { if (!IsElement()) JSObject::ReoptimizeIfPrototype(receiver); return; } } else { pair = AccessorPair::Copy(isolate(), pair); pair->SetComponents(*getter, *setter); } } else { pair = factory()->NewAccessorPair(); pair->SetComponents(*getter, *setter); } TransitionToAccessorPair(pair, attributes); #if VERIFY_HEAP if (FLAG_verify_heap) { receiver->JSObjectVerify(isolate()); } #endif } void LookupIterator::TransitionToAccessorPair(Handle pair, PropertyAttributes attributes) { Handle receiver = GetStoreTarget(); holder_ = receiver; PropertyDetails details(kAccessor, attributes, PropertyCellType::kMutable); if (IsElement()) { // TODO(verwaest): Move code into the element accessor. isolate_->CountUsage(v8::Isolate::kIndexAccessor); Handle dictionary = JSObject::NormalizeElements(receiver); dictionary = NumberDictionary::Set(isolate_, dictionary, index_, pair, receiver, details); receiver->RequireSlowElements(*dictionary); if (receiver->HasSlowArgumentsElements()) { FixedArray* parameter_map = FixedArray::cast(receiver->elements()); uint32_t length = parameter_map->length() - 2; if (number_ < length) { parameter_map->set(number_ + 2, ReadOnlyRoots(heap()).the_hole_value()); } FixedArray::cast(receiver->elements())->set(1, *dictionary); } else { receiver->set_elements(*dictionary); } ReloadPropertyInformation(); } else { PropertyNormalizationMode mode = CLEAR_INOBJECT_PROPERTIES; if (receiver->map()->is_prototype_map()) { JSObject::InvalidatePrototypeChains(receiver->map()); mode = KEEP_INOBJECT_PROPERTIES; } // Normalize object to make this operation simple. JSObject::NormalizeProperties(receiver, mode, 0, "TransitionToAccessorPair"); JSObject::SetNormalizedProperty(receiver, name_, pair, details); JSObject::ReoptimizeIfPrototype(receiver); ReloadPropertyInformation(); } } bool LookupIterator::HolderIsReceiver() const { DCHECK(has_property_ || state_ == INTERCEPTOR || state_ == JSPROXY); // Optimization that only works if configuration_ is not mutable. if (!check_prototype_chain()) return true; return *receiver_ == *holder_; } bool LookupIterator::HolderIsReceiverOrHiddenPrototype() const { DCHECK(has_property_ || state_ == INTERCEPTOR || state_ == JSPROXY); // Optimization that only works if configuration_ is not mutable. if (!check_prototype_chain()) return true; DisallowHeapAllocation no_gc; if (*receiver_ == *holder_) return true; if (!receiver_->IsJSReceiver()) return false; JSReceiver* current = JSReceiver::cast(*receiver_); JSReceiver* object = *holder_; if (!current->map()->has_hidden_prototype()) return false; // JSProxy do not occur as hidden prototypes. if (object->IsJSProxy()) return false; PrototypeIterator iter(isolate(), current, kStartAtPrototype, PrototypeIterator::END_AT_NON_HIDDEN); while (!iter.IsAtEnd()) { if (iter.GetCurrent() == object) return true; iter.Advance(); } return false; } Handle LookupIterator::FetchValue() const { Object* result = nullptr; if (IsElement()) { Handle holder = GetHolder(); ElementsAccessor* accessor = holder->GetElementsAccessor(); return accessor->Get(holder, number_); } else if (holder_->IsJSGlobalObject()) { Handle holder = GetHolder(); result = holder->global_dictionary()->ValueAt(number_); } else if (!holder_->HasFastProperties()) { result = holder_->property_dictionary()->ValueAt(number_); } else if (property_details_.location() == kField) { DCHECK_EQ(kData, property_details_.kind()); Handle holder = GetHolder(); FieldIndex field_index = FieldIndex::ForDescriptor(holder->map(), number_); return JSObject::FastPropertyAt(holder, property_details_.representation(), field_index); } else { result = holder_->map()->instance_descriptors()->GetStrongValue(number_); } return handle(result, isolate_); } bool LookupIterator::IsConstFieldValueEqualTo(Object* value) const { DCHECK(!IsElement()); DCHECK(holder_->HasFastProperties()); DCHECK_EQ(kField, property_details_.location()); DCHECK_EQ(PropertyConstness::kConst, property_details_.constness()); Handle holder = GetHolder(); FieldIndex field_index = FieldIndex::ForDescriptor(holder->map(), number_); if (property_details_.representation().IsDouble()) { if (!value->IsNumber()) return false; uint64_t bits; if (holder->IsUnboxedDoubleField(field_index)) { bits = holder->RawFastDoublePropertyAsBitsAt(field_index); } else { Object* current_value = holder->RawFastPropertyAt(field_index); DCHECK(current_value->IsMutableHeapNumber()); bits = MutableHeapNumber::cast(current_value)->value_as_bits(); } // Use bit representation of double to to check for hole double, since // manipulating the signaling NaN used for the hole in C++, e.g. with // bit_cast or value(), will change its value on ia32 (the x87 stack is // used to return values and stores to the stack silently clear the // signalling bit). if (bits == kHoleNanInt64) { // Uninitialized double field. return true; } return bit_cast(bits) == value->Number(); } else { Object* current_value = holder->RawFastPropertyAt(field_index); return current_value->IsUninitialized(isolate()) || current_value == value; } } int LookupIterator::GetFieldDescriptorIndex() const { DCHECK(has_property_); DCHECK(holder_->HasFastProperties()); DCHECK_EQ(kField, property_details_.location()); DCHECK_EQ(kData, property_details_.kind()); return descriptor_number(); } int LookupIterator::GetAccessorIndex() const { DCHECK(has_property_); DCHECK(holder_->HasFastProperties()); DCHECK_EQ(kDescriptor, property_details_.location()); DCHECK_EQ(kAccessor, property_details_.kind()); return descriptor_number(); } int LookupIterator::GetConstantIndex() const { DCHECK(has_property_); DCHECK(holder_->HasFastProperties()); DCHECK_EQ(kDescriptor, property_details_.location()); DCHECK_EQ(kData, property_details_.kind()); DCHECK(!FLAG_track_constant_fields); DCHECK(!IsElement()); return descriptor_number(); } Handle LookupIterator::GetFieldOwnerMap() const { DCHECK(has_property_); DCHECK(holder_->HasFastProperties()); DCHECK_EQ(kField, property_details_.location()); DCHECK(!IsElement()); Map* holder_map = holder_->map(); return handle(holder_map->FindFieldOwner(isolate(), descriptor_number()), isolate_); } FieldIndex LookupIterator::GetFieldIndex() const { DCHECK(has_property_); DCHECK(holder_->HasFastProperties()); DCHECK_EQ(kField, property_details_.location()); DCHECK(!IsElement()); return FieldIndex::ForDescriptor(holder_->map(), descriptor_number()); } Handle LookupIterator::GetFieldType() const { DCHECK(has_property_); DCHECK(holder_->HasFastProperties()); DCHECK_EQ(kField, property_details_.location()); return handle( holder_->map()->instance_descriptors()->GetFieldType(descriptor_number()), isolate_); } Handle LookupIterator::GetPropertyCell() const { DCHECK(!IsElement()); Handle holder = GetHolder(); return handle(holder->global_dictionary()->CellAt(dictionary_entry()), isolate_); } Handle LookupIterator::GetAccessors() const { DCHECK_EQ(ACCESSOR, state_); return FetchValue(); } Handle LookupIterator::GetDataValue() const { DCHECK_EQ(DATA, state_); Handle value = FetchValue(); return value; } void LookupIterator::WriteDataValue(Handle value, bool initializing_store) { DCHECK_EQ(DATA, state_); Handle holder = GetHolder(); if (IsElement()) { Handle object = Handle::cast(holder); ElementsAccessor* accessor = object->GetElementsAccessor(); accessor->Set(object, number_, *value); } else if (holder->HasFastProperties()) { if (property_details_.location() == kField) { // Check that in case of VariableMode::kConst field the existing value is // equal to |value|. DCHECK_IMPLIES(!initializing_store && property_details_.constness() == PropertyConstness::kConst, IsConstFieldValueEqualTo(*value)); JSObject::cast(*holder)->WriteToField(descriptor_number(), property_details_, *value); } else { DCHECK_EQ(kDescriptor, property_details_.location()); DCHECK_EQ(PropertyConstness::kConst, property_details_.constness()); } } else if (holder->IsJSGlobalObject()) { GlobalDictionary* dictionary = JSGlobalObject::cast(*holder)->global_dictionary(); dictionary->CellAt(dictionary_entry())->set_value(*value); } else { DCHECK_IMPLIES(holder->IsJSProxy(), name()->IsPrivate()); NameDictionary* dictionary = holder->property_dictionary(); dictionary->ValueAtPut(dictionary_entry(), *value); } } template bool LookupIterator::SkipInterceptor(JSObject* holder) { auto info = GetInterceptor(holder); if (!is_element && name_->IsSymbol() && !info->can_intercept_symbols()) { return true; } if (info->non_masking()) { switch (interceptor_state_) { case InterceptorState::kUninitialized: interceptor_state_ = InterceptorState::kSkipNonMasking; V8_FALLTHROUGH; case InterceptorState::kSkipNonMasking: return true; case InterceptorState::kProcessNonMasking: return false; } } return interceptor_state_ == InterceptorState::kProcessNonMasking; } JSReceiver* LookupIterator::NextHolder(Map* map) { DisallowHeapAllocation no_gc; if (map->prototype() == ReadOnlyRoots(heap()).null_value()) return nullptr; if (!check_prototype_chain() && !map->has_hidden_prototype()) return nullptr; return JSReceiver::cast(map->prototype()); } LookupIterator::State LookupIterator::NotFound(JSReceiver* const holder) const { DCHECK(!IsElement()); if (!holder->IsJSTypedArray() || !name_->IsString()) return NOT_FOUND; Handle name_string = Handle::cast(name_); if (name_string->length() == 0) return NOT_FOUND; return IsSpecialIndex(isolate_->unicode_cache(), *name_string) ? INTEGER_INDEXED_EXOTIC : NOT_FOUND; } namespace { template bool HasInterceptor(Map* map) { return is_element ? map->has_indexed_interceptor() : map->has_named_interceptor(); } } // namespace template LookupIterator::State LookupIterator::LookupInSpecialHolder( Map* const map, JSReceiver* const holder) { STATIC_ASSERT(INTERCEPTOR == BEFORE_PROPERTY); switch (state_) { case NOT_FOUND: if (map->IsJSProxyMap()) { if (is_element || !name_->IsPrivate()) return JSPROXY; } if (map->is_access_check_needed()) { if (is_element || !name_->IsPrivate()) return ACCESS_CHECK; } V8_FALLTHROUGH; case ACCESS_CHECK: if (check_interceptor() && HasInterceptor(map) && !SkipInterceptor(JSObject::cast(holder))) { if (is_element || !name_->IsPrivate()) return INTERCEPTOR; } V8_FALLTHROUGH; case INTERCEPTOR: if (!is_element && map->IsJSGlobalObjectMap()) { GlobalDictionary* dict = JSGlobalObject::cast(holder)->global_dictionary(); int number = dict->FindEntry(isolate(), name_); if (number == GlobalDictionary::kNotFound) return NOT_FOUND; number_ = static_cast(number); PropertyCell* cell = dict->CellAt(number_); if (cell->value()->IsTheHole(isolate_)) return NOT_FOUND; property_details_ = cell->property_details(); has_property_ = true; switch (property_details_.kind()) { case v8::internal::kData: return DATA; case v8::internal::kAccessor: return ACCESSOR; } } return LookupInRegularHolder(map, holder); case ACCESSOR: case DATA: return NOT_FOUND; case INTEGER_INDEXED_EXOTIC: case JSPROXY: case TRANSITION: UNREACHABLE(); } UNREACHABLE(); } template LookupIterator::State LookupIterator::LookupInRegularHolder( Map* const map, JSReceiver* const holder) { DisallowHeapAllocation no_gc; if (interceptor_state_ == InterceptorState::kProcessNonMasking) { return NOT_FOUND; } if (is_element) { JSObject* js_object = JSObject::cast(holder); ElementsAccessor* accessor = js_object->GetElementsAccessor(); FixedArrayBase* backing_store = js_object->elements(); number_ = accessor->GetEntryForIndex(isolate_, js_object, backing_store, index_); if (number_ == kMaxUInt32) { return holder->IsJSTypedArray() ? INTEGER_INDEXED_EXOTIC : NOT_FOUND; } property_details_ = accessor->GetDetails(js_object, number_); } else if (!map->is_dictionary_map()) { DescriptorArray* descriptors = map->instance_descriptors(); int number = descriptors->SearchWithCache(isolate_, *name_, map); if (number == DescriptorArray::kNotFound) return NotFound(holder); number_ = static_cast(number); property_details_ = descriptors->GetDetails(number_); } else { DCHECK_IMPLIES(holder->IsJSProxy(), name()->IsPrivate()); NameDictionary* dict = holder->property_dictionary(); int number = dict->FindEntry(isolate(), name_); if (number == NameDictionary::kNotFound) return NotFound(holder); number_ = static_cast(number); property_details_ = dict->DetailsAt(number_); } has_property_ = true; switch (property_details_.kind()) { case v8::internal::kData: return DATA; case v8::internal::kAccessor: return ACCESSOR; } UNREACHABLE(); } Handle LookupIterator::GetInterceptorForFailedAccessCheck() const { DCHECK_EQ(ACCESS_CHECK, state_); DisallowHeapAllocation no_gc; AccessCheckInfo* access_check_info = AccessCheckInfo::Get(isolate_, Handle::cast(holder_)); if (access_check_info) { Object* interceptor = IsElement() ? access_check_info->indexed_interceptor() : access_check_info->named_interceptor(); if (interceptor) { return handle(InterceptorInfo::cast(interceptor), isolate_); } } return Handle(); } bool LookupIterator::TryLookupCachedProperty() { return state() == LookupIterator::ACCESSOR && GetAccessors()->IsAccessorPair() && LookupCachedProperty(); } bool LookupIterator::LookupCachedProperty() { DCHECK_EQ(state(), LookupIterator::ACCESSOR); DCHECK(GetAccessors()->IsAccessorPair()); AccessorPair* accessor_pair = AccessorPair::cast(*GetAccessors()); Handle getter(accessor_pair->getter(), isolate()); MaybeHandle maybe_name = FunctionTemplateInfo::TryGetCachedPropertyName(isolate(), getter); if (maybe_name.is_null()) return false; // We have found a cached property! Modify the iterator accordingly. name_ = maybe_name.ToHandleChecked(); Restart(); CHECK_EQ(state(), LookupIterator::DATA); return true; } } // namespace internal } // namespace v8