// 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" namespace v8 { namespace internal { // static LookupIterator LookupIterator::PropertyOrElement(Isolate* isolate, Handle<Object> receiver, Handle<Object> key, bool* success, Configuration configuration) { uint32_t index = 0; if (key->ToArrayIndex(&index)) { *success = true; return LookupIterator(isolate, receiver, index, configuration); } Handle<Name> name; *success = Object::ToName(isolate, key).ToHandle(&name); if (!*success) { DCHECK(isolate->has_pending_exception()); // Return an unusable dummy. return LookupIterator(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(receiver, name, configuration); } template <bool is_element> void LookupIterator::Start() { DisallowHeapAllocation no_gc; has_property_ = false; state_ = NOT_FOUND; holder_ = initial_holder_; JSReceiver* holder = *holder_; Map* map = holder->map(); state_ = LookupInHolder<is_element>(map, holder); if (IsFound()) return; NextInternal<is_element>(map, holder); } template void LookupIterator::Start<true>(); template void LookupIterator::Start<false>(); 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<true>(map, holder) : LookupInSpecialHolder<false>(map, holder); if (IsFound()) return; } IsElement() ? NextInternal<true>(map, holder) : NextInternal<false>(map, holder); } template <bool is_element> void LookupIterator::NextInternal(Map* map, JSReceiver* holder) { do { JSReceiver* maybe_holder = NextHolder(map); if (maybe_holder == nullptr) { if (interceptor_state_ == InterceptorState::kSkipNonMasking) { RestartLookupForNonMaskingInterceptors<is_element>(); return; } state_ = NOT_FOUND; if (holder != *holder_) holder_ = handle(holder, isolate_); return; } holder = maybe_holder; map = holder->map(); state_ = LookupInHolder<is_element>(map, holder); } while (!IsFound()); holder_ = handle(holder, isolate_); } template <bool is_element> void LookupIterator::RestartInternal(InterceptorState interceptor_state) { interceptor_state_ = interceptor_state; property_details_ = PropertyDetails::Empty(); number_ = DescriptorArray::kNotFound; Start<is_element>(); } template void LookupIterator::RestartInternal<true>(InterceptorState); template void LookupIterator::RestartInternal<false>(InterceptorState); // static Handle<JSReceiver> LookupIterator::GetRootForNonJSReceiver( Isolate* isolate, Handle<Object> 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<uint32_t>(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<JSFunction> constructor = isolate->string_function(); Handle<JSObject> result = isolate->factory()->NewJSObject(constructor); Handle<JSValue>::cast(result)->set_value(*receiver); return result; } auto root = handle(receiver->GetPrototypeChainRootMap(isolate)->prototype(), isolate); if (root->IsNull(isolate)) { unsigned int magic = 0xbbbbbbbb; isolate->PushStackTraceAndDie(magic, *receiver, NULL, magic); } return Handle<JSReceiver>::cast(root); } Handle<Map> LookupIterator::GetReceiverMap() const { if (receiver_->IsNumber()) return factory()->heap_number_map(); return handle(Handle<HeapObject>::cast(receiver_)->map(), isolate_); } bool LookupIterator::HasAccess() const { DCHECK_EQ(ACCESS_CHECK, state_); return isolate_->MayAccess(handle(isolate_->context()), GetHolder<JSObject>()); } template <bool is_element> void LookupIterator::ReloadPropertyInformation() { state_ = BEFORE_PROPERTY; interceptor_state_ = InterceptorState::kUninitialized; state_ = LookupInHolder<is_element>(holder_->map(), *holder_); DCHECK(IsFound() || !holder_->HasFastProperties()); } void LookupIterator::InternalUpdateProtector() { if (isolate_->bootstrapper()->IsActive()) return; if (*name_ == heap()->constructor_string()) { if (!isolate_->IsArraySpeciesLookupChainIntact()) return; // Setting the constructor property could change an instance's @@species if (holder_->IsJSArray()) { isolate_->CountUsage( v8::Isolate::UseCounterFeature::kArrayInstanceConstructorModified); isolate_->InvalidateArraySpeciesProtector(); } else if (holder_->map()->is_prototype_map()) { DisallowHeapAllocation no_gc; // Setting the constructor of Array.prototype of any realm also needs // to invalidate the species protector if (isolate_->IsInAnyContext(*holder_, Context::INITIAL_ARRAY_PROTOTYPE_INDEX)) { isolate_->CountUsage(v8::Isolate::UseCounterFeature:: kArrayPrototypeConstructorModified); isolate_->InvalidateArraySpeciesProtector(); } } } else if (*name_ == heap()->species_symbol()) { if (!isolate_->IsArraySpeciesLookupChainIntact()) return; // Setting the Symbol.species property of any Array constructor invalidates // the species protector if (isolate_->IsInAnyContext(*holder_, Context::ARRAY_FUNCTION_INDEX)) { isolate_->CountUsage( v8::Isolate::UseCounterFeature::kArraySpeciesModified); isolate_->InvalidateArraySpeciesProtector(); } } else if (*name_ == heap()->is_concat_spreadable_symbol()) { if (!isolate_->IsIsConcatSpreadableLookupChainIntact()) return; isolate_->InvalidateIsConcatSpreadableProtector(); } else if (*name_ == heap()->iterator_symbol()) { if (!isolate_->IsArrayIteratorLookupChainIntact()) return; if (holder_->IsJSArray()) { isolate_->InvalidateArrayIteratorProtector(); } } } void LookupIterator::PrepareForDataProperty(Handle<Object> value) { DCHECK(state_ == DATA || state_ == ACCESSOR); DCHECK(HolderIsReceiverOrHiddenPrototype()); Handle<JSObject> holder = GetHolder<JSObject>(); if (IsElement()) { ElementsKind kind = holder->GetElementsKind(); ElementsKind to = value->OptimalElementsKind(); if (IsHoleyElementsKind(kind)) to = GetHoleyElementsKind(to); to = GetMoreGeneralElementsKind(kind, to); if (kind != to) { JSObject::TransitionElementsKind(holder, to); } // Copy the backing store if it is copy-on-write. if (IsFastSmiOrObjectElementsKind(to)) { JSObject::EnsureWritableFastElements(holder); } return; } if (holder->IsJSGlobalObject()) { Handle<GlobalDictionary> dictionary(holder->global_dictionary()); Handle<PropertyCell> cell( PropertyCell::cast(dictionary->ValueAt(dictionary_entry()))); DCHECK(!cell->IsTheHole(isolate_)); property_details_ = cell->property_details(); PropertyCell::PrepareForValue(dictionary, dictionary_entry(), value, property_details_); return; } if (!holder->HasFastProperties()) return; PropertyConstness new_constness = kConst; if (FLAG_track_constant_fields) { if (constness() == 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 = kMutable; } } else { new_constness = kMutable; } Handle<Map> old_map(holder->map(), isolate_); Handle<Map> new_map = Map::PrepareForDataProperty( 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 (representation().IsNone()) { property_details_ = new_map->instance_descriptors()->GetDetails(descriptor_number()); } return; } JSObject::MigrateToMap(holder, new_map); ReloadPropertyInformation<false>(); } void LookupIterator::ReconfigureDataProperty(Handle<Object> value, PropertyAttributes attributes) { DCHECK(state_ == DATA || state_ == ACCESSOR); DCHECK(HolderIsReceiverOrHiddenPrototype()); Handle<JSObject> holder = GetHolder<JSObject>(); if (IsElement()) { DCHECK(!holder->HasFixedTypedArrayElements()); DCHECK(attributes != NONE || !holder->HasFastElements()); Handle<FixedArrayBase> elements(holder->elements()); holder->GetElementsAccessor()->Reconfigure(holder, elements, number_, value, attributes); ReloadPropertyInformation<true>(); } else if (holder->HasFastProperties()) { Handle<Map> old_map(holder->map(), isolate_); Handle<Map> new_map = Map::ReconfigureExistingProperty( old_map, descriptor_number(), i::kData, attributes); // Force mutable to avoid changing constant value by reconfiguring // kData -> kAccessor -> kData. new_map = Map::PrepareForDataProperty(new_map, descriptor_number(), kMutable, value); JSObject::MigrateToMap(holder, new_map); ReloadPropertyInformation<false>(); } else { PropertyDetails details(kData, attributes, 0, PropertyCellType::kMutable); if (holder->IsJSGlobalObject()) { Handle<GlobalDictionary> dictionary(holder->global_dictionary()); Handle<PropertyCell> cell = PropertyCell::PrepareForValue( dictionary, dictionary_entry(), value, details); cell->set_value(*value); property_details_ = cell->property_details(); } else { Handle<NameDictionary> dictionary(holder->property_dictionary()); PropertyDetails original_details = dictionary->DetailsAt(dictionary_entry()); int enumeration_index = original_details.dictionary_index(); DCHECK(enumeration_index > 0); details = details.set_index(enumeration_index); dictionary->SetEntry(dictionary_entry(), name(), value, details); property_details_ = details; } state_ = DATA; } WriteDataValue(value, true); #if VERIFY_HEAP if (FLAG_verify_heap) { holder->JSObjectVerify(); } #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<JSObject> receiver, Handle<Object> value, PropertyAttributes attributes, Object::StoreFromKeyed store_mode) { DCHECK(receiver.is_identical_to(GetStoreTarget())); if (state_ == TRANSITION) return; if (!IsElement() && name()->IsPrivate()) { attributes = static_cast<PropertyAttributes>(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> 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<JSGlobalObject> global = Handle<JSGlobalObject>::cast(receiver); int entry; Handle<PropertyCell> cell = JSGlobalObject::EnsureEmptyPropertyCell( global, name(), PropertyCellType::kUninitialized, &entry); Handle<GlobalDictionary> 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, index, PropertyCellType::kUninitialized); PropertyCellType new_type = PropertyCell::UpdatedType(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, 0, PropertyCellType::kNoCell); transition_ = map; } return; } Handle<Map> transition = Map::TransitionToDataProperty( map, name_, value, attributes, kDefaultFieldConstness, store_mode); 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, 0, PropertyCellType::kNoCell); } else { property_details_ = transition->GetLastDescriptorDetails(); has_property_ = true; } } void LookupIterator::ApplyTransitionToDataProperty(Handle<JSObject> receiver) { DCHECK_EQ(TRANSITION, state_); DCHECK(receiver.is_identical_to(GetStoreTarget())); holder_ = receiver; if (receiver->IsJSGlobalObject()) { state_ = DATA; return; } Handle<Map> transition = transition_map(); bool simple_transition = transition->GetBackPointer() == receiver->map(); JSObject::MigrateToMap(receiver, transition); if (simple_transition) { int number = transition->LastAdded(); number_ = static_cast<uint32_t>(number); property_details_ = transition->GetLastDescriptorDetails(); state_ = DATA; } else if (receiver->map()->is_dictionary_map()) { Handle<NameDictionary> dictionary(receiver->property_dictionary(), isolate_); int entry; dictionary = NameDictionary::Add(dictionary, name(), isolate_->factory()->uninitialized_value(), property_details_, &entry); receiver->set_properties(*dictionary); // Reload details containing proper enumeration index value. property_details_ = dictionary->DetailsAt(entry); number_ = entry; has_property_ = true; state_ = DATA; } else { ReloadPropertyInformation<false>(); } } void LookupIterator::Delete() { Handle<JSReceiver> holder = Handle<JSReceiver>::cast(holder_); if (IsElement()) { Handle<JSObject> object = Handle<JSObject>::cast(holder); ElementsAccessor* accessor = object->GetElementsAccessor(); accessor->Delete(object, number_); } else { bool is_prototype_map = holder->map()->is_prototype_map(); RuntimeCallTimerScope stats_scope( isolate_, is_prototype_map ? &RuntimeCallStats::PrototypeObject_DeleteProperty : &RuntimeCallStats::Object_DeleteProperty); PropertyNormalizationMode mode = is_prototype_map ? KEEP_INOBJECT_PROPERTIES : CLEAR_INOBJECT_PROPERTIES; if (holder->HasFastProperties()) { JSObject::NormalizeProperties(Handle<JSObject>::cast(holder), mode, 0, "DeletingProperty"); ReloadPropertyInformation<false>(); } // TODO(verwaest): Get rid of the name_ argument. JSReceiver::DeleteNormalizedProperty(holder, name_, number_); if (holder->IsJSObject()) { JSObject::ReoptimizeIfPrototype(Handle<JSObject>::cast(holder)); } } state_ = NOT_FOUND; } void LookupIterator::TransitionToAccessorProperty( Handle<Object> getter, Handle<Object> 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<JSObject> receiver = GetStoreTarget(); if (!IsElement() && name()->IsPrivate()) { attributes = static_cast<PropertyAttributes>(attributes | DONT_ENUM); } if (!IsElement() && !receiver->map()->is_dictionary_map()) { Handle<Map> old_map(receiver->map(), isolate_); if (!holder_.is_identical_to(receiver)) { holder_ = receiver; state_ = NOT_FOUND; } else if (state_ == INTERCEPTOR) { LookupInRegularHolder<false>(*old_map, *holder_); } int descriptor = IsFound() ? static_cast<int>(number_) : DescriptorArray::kNotFound; Handle<Map> 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<uint32_t>(number); property_details_ = new_map->GetLastDescriptorDetails(); state_ = ACCESSOR; return; } ReloadPropertyInformation<false>(); if (!new_map->is_dictionary_map()) return; } Handle<AccessorPair> pair; if (state() == ACCESSOR && GetAccessors()->IsAccessorPair()) { pair = Handle<AccessorPair>::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(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(); } #endif } void LookupIterator::TransitionToAccessorPair(Handle<Object> pair, PropertyAttributes attributes) { Handle<JSObject> receiver = GetStoreTarget(); holder_ = receiver; PropertyDetails details(kAccessor, attributes, 0, PropertyCellType::kMutable); if (IsElement()) { // TODO(verwaest): Move code into the element accessor. Handle<SeededNumberDictionary> dictionary = JSObject::NormalizeElements(receiver); dictionary = SeededNumberDictionary::Set(dictionary, index_, pair, details, receiver); 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, heap()->the_hole_value()); } FixedArray::cast(receiver->elements())->set(1, *dictionary); } else { receiver->set_elements(*dictionary); } ReloadPropertyInformation<true>(); } else { PropertyNormalizationMode mode = receiver->map()->is_prototype_map() ? KEEP_INOBJECT_PROPERTIES : CLEAR_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<false>(); } } 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<JSReceiver>() == object) return true; iter.Advance(); } return false; } Handle<Object> LookupIterator::FetchValue() const { Object* result = NULL; if (IsElement()) { Handle<JSObject> holder = GetHolder<JSObject>(); ElementsAccessor* accessor = holder->GetElementsAccessor(); return accessor->Get(holder, number_); } else if (holder_->IsJSGlobalObject()) { Handle<JSObject> holder = GetHolder<JSObject>(); result = holder->global_dictionary()->ValueAt(number_); DCHECK(result->IsPropertyCell()); result = PropertyCell::cast(result)->value(); } else if (!holder_->HasFastProperties()) { result = holder_->property_dictionary()->ValueAt(number_); } else if (property_details_.location() == kField) { DCHECK_EQ(kData, property_details_.kind()); Handle<JSObject> holder = GetHolder<JSObject>(); FieldIndex field_index = FieldIndex::ForDescriptor(holder->map(), number_); return JSObject::FastPropertyAt(holder, property_details_.representation(), field_index); } else { result = holder_->map()->instance_descriptors()->GetValue(number_); } return handle(result, isolate_); } bool LookupIterator::IsConstFieldValueEqualTo(Object* value) const { DCHECK(!IsElement()); DCHECK(holder_->HasFastProperties()); DCHECK_EQ(kField, property_details_.location()); DCHECK_EQ(kConst, property_details_.constness()); Handle<JSObject> holder = GetHolder<JSObject>(); 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 = HeapNumber::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<double>(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<Map> 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(descriptor_number()), isolate_); } FieldIndex LookupIterator::GetFieldIndex() const { DCHECK(has_property_); DCHECK(holder_->HasFastProperties()); DCHECK_EQ(kField, property_details_.location()); DCHECK(!IsElement()); Map* holder_map = holder_->map(); int index = holder_map->instance_descriptors()->GetFieldIndex(descriptor_number()); bool is_double = representation().IsDouble(); return FieldIndex::ForPropertyIndex(holder_map, index, is_double); } Handle<FieldType> 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<PropertyCell> LookupIterator::GetPropertyCell() const { DCHECK(!IsElement()); Handle<JSGlobalObject> holder = GetHolder<JSGlobalObject>(); Object* value = holder->global_dictionary()->ValueAt(dictionary_entry()); DCHECK(value->IsPropertyCell()); return handle(PropertyCell::cast(value), isolate_); } Handle<Object> LookupIterator::GetAccessors() const { DCHECK_EQ(ACCESSOR, state_); return FetchValue(); } Handle<Object> LookupIterator::GetDataValue() const { DCHECK_EQ(DATA, state_); Handle<Object> value = FetchValue(); return value; } void LookupIterator::WriteDataValue(Handle<Object> value, bool initializing_store) { DCHECK_EQ(DATA, state_); Handle<JSReceiver> holder = GetHolder<JSReceiver>(); if (IsElement()) { Handle<JSObject> object = Handle<JSObject>::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 kConst field the existing value is equal to // |value|. DCHECK_IMPLIES( !initializing_store && property_details_.constness() == kConst, IsConstFieldValueEqualTo(*value)); JSObject::cast(*holder)->WriteToField(descriptor_number(), property_details_, *value); } else { DCHECK_EQ(kDescriptor, property_details_.location()); DCHECK_EQ(kConst, property_details_.constness()); } } else if (holder->IsJSGlobalObject()) { GlobalDictionary* dictionary = JSObject::cast(*holder)->global_dictionary(); Object* cell = dictionary->ValueAt(dictionary_entry()); DCHECK(cell->IsPropertyCell()); PropertyCell::cast(cell)->set_value(*value); } else { NameDictionary* dictionary = holder->property_dictionary(); dictionary->ValueAtPut(dictionary_entry(), *value); } } template <bool is_element> bool LookupIterator::SkipInterceptor(JSObject* holder) { auto info = GetInterceptor<is_element>(holder); // TODO(dcarney): check for symbol/can_intercept_symbols here as well. if (info->non_masking()) { switch (interceptor_state_) { case InterceptorState::kUninitialized: interceptor_state_ = InterceptorState::kSkipNonMasking; // Fall through. 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() == heap()->null_value()) return NULL; if (!check_prototype_chain() && !map->has_hidden_prototype()) return NULL; return JSReceiver::cast(map->prototype()); } LookupIterator::State LookupIterator::NotFound(JSReceiver* const holder) const { DCHECK(!IsElement()); if (!holder->IsJSTypedArray() || !name_->IsString()) return NOT_FOUND; Handle<String> name_string = Handle<String>::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 is_element> bool HasInterceptor(Map* map) { return is_element ? map->has_indexed_interceptor() : map->has_named_interceptor(); } } // namespace template <bool is_element> 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; } // Fall through. case ACCESS_CHECK: if (check_interceptor() && HasInterceptor<is_element>(map) && !SkipInterceptor<is_element>(JSObject::cast(holder))) { if (is_element || !name_->IsPrivate()) return INTERCEPTOR; } // Fall through. case INTERCEPTOR: if (!is_element && map->IsJSGlobalObjectMap()) { GlobalDictionary* dict = JSObject::cast(holder)->global_dictionary(); int number = dict->FindEntry(name_); if (number == GlobalDictionary::kNotFound) return NOT_FOUND; number_ = static_cast<uint32_t>(number); DCHECK(dict->ValueAt(number_)->IsPropertyCell()); PropertyCell* cell = PropertyCell::cast(dict->ValueAt(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<is_element>(map, holder); case ACCESSOR: case DATA: return NOT_FOUND; case INTEGER_INDEXED_EXOTIC: case JSPROXY: case TRANSITION: UNREACHABLE(); } UNREACHABLE(); return NOT_FOUND; } template <bool is_element> 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<uint32_t>(number); property_details_ = descriptors->GetDetails(number_); } else { NameDictionary* dict = holder->property_dictionary(); int number = dict->FindEntry(name_); if (number == NameDictionary::kNotFound) return NotFound(holder); number_ = static_cast<uint32_t>(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(); return state_; } Handle<InterceptorInfo> LookupIterator::GetInterceptorForFailedAccessCheck() const { DCHECK_EQ(ACCESS_CHECK, state_); DisallowHeapAllocation no_gc; AccessCheckInfo* access_check_info = AccessCheckInfo::Get(isolate_, Handle<JSObject>::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<InterceptorInfo>(); } 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<Object> getter(accessor_pair->getter(), isolate()); MaybeHandle<Name> 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