// 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/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); } void LookupIterator::Next() { DCHECK_NE(JSPROXY, state_); DCHECK_NE(TRANSITION, state_); DisallowHeapAllocation no_gc; has_property_ = false; JSReceiver* holder = *holder_; Map* map = *holder_map_; // Perform lookup on current holder. state_ = LookupInHolder(map, holder); if (IsFound()) return; // Continue lookup if lookup on current holder failed. do { JSReceiver* maybe_holder = NextHolder(map); if (maybe_holder == nullptr) { if (interceptor_state_ == InterceptorState::kSkipNonMasking) { RestartLookupForNonMaskingInterceptors(); return; } break; } holder = maybe_holder; map = holder->map(); state_ = LookupInHolder(map, holder); } while (!IsFound()); if (holder != *holder_) { holder_ = handle(holder, isolate_); holder_map_ = handle(map, isolate_); } } void LookupIterator::RestartInternal(InterceptorState interceptor_state) { state_ = NOT_FOUND; interceptor_state_ = interceptor_state; property_details_ = PropertyDetails::Empty(); holder_ = initial_holder_; holder_map_ = handle(holder_->map(), isolate_); number_ = DescriptorArray::kNotFound; Next(); } // 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->GetRootMap(isolate)->prototype(), isolate); if (root->IsNull()) { 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_); } Handle<JSObject> LookupIterator::GetStoreTarget() const { if (receiver_->IsJSGlobalProxy()) { PrototypeIterator iter(isolate(), receiver_); if (iter.IsAtEnd()) return Handle<JSGlobalProxy>::cast(receiver_); return PrototypeIterator::GetCurrent<JSGlobalObject>(iter); } return Handle<JSObject>::cast(receiver_); } bool LookupIterator::HasAccess() const { DCHECK_EQ(ACCESS_CHECK, state_); return isolate_->MayAccess(handle(isolate_->context()), GetHolder<JSObject>()); } void LookupIterator::ReloadPropertyInformation() { state_ = BEFORE_PROPERTY; interceptor_state_ = InterceptorState::kUninitialized; state_ = LookupInHolder(*holder_map_, *holder_); DCHECK(IsFound() || holder_map_->is_dictionary_map()); } void LookupIterator::ReloadHolderMap() { DCHECK_EQ(DATA, state_); DCHECK(IsElement()); DCHECK(JSObject::cast(*holder_)->HasFixedTypedArrayElements()); if (*holder_map_ != holder_->map()) { holder_map_ = handle(holder_->map(), isolate_); } } void LookupIterator::PrepareForDataProperty(Handle<Object> value) { DCHECK(state_ == DATA || state_ == ACCESSOR); DCHECK(HolderIsReceiverOrHiddenPrototype()); Handle<JSObject> holder = GetHolder<JSObject>(); if (IsElement()) { ElementsKind kind = holder_map_->elements_kind(); ElementsKind to = value->OptimalElementsKind(); if (IsHoleyElementsKind(kind)) to = GetHoleyElementsKind(to); to = GetMoreGeneralElementsKind(kind, to); JSObject::TransitionElementsKind(holder, to); holder_map_ = handle(holder->map(), isolate_); // Copy the backing store if it is copy-on-write. if (IsFastSmiOrObjectElementsKind(to)) { JSObject::EnsureWritableFastElements(holder); } } else { if (holder_map_->is_dictionary_map()) return; holder_map_ = Map::PrepareForDataProperty(holder_map_, descriptor_number(), value); } JSObject::MigrateToMap(holder, holder_map_); ReloadPropertyInformation(); } 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); } else if (holder_map_->is_dictionary_map()) { PropertyDetails details(attributes, v8::internal::DATA, 0, PropertyCellType::kMutable); JSObject::SetNormalizedProperty(holder, name(), value, details); } else { holder_map_ = Map::ReconfigureExistingProperty( holder_map_, descriptor_number(), i::kData, attributes); holder_map_ = Map::PrepareForDataProperty(holder_map_, descriptor_number(), value); JSObject::MigrateToMap(holder, holder_map_); } ReloadPropertyInformation(); WriteDataValue(value); #if VERIFY_HEAP if (FLAG_verify_heap) { holder->JSObjectVerify(); } #endif } void LookupIterator::PrepareTransitionToDataProperty( Handle<Object> value, PropertyAttributes attributes, Object::StoreFromKeyed store_mode) { if (state_ == TRANSITION) return; DCHECK(state_ != LookupIterator::ACCESSOR || (GetAccessors()->IsAccessorInfo() && AccessorInfo::cast(*GetAccessors())->is_special_data_property())); DCHECK_NE(INTEGER_INDEXED_EXOTIC, state_); DCHECK(state_ == NOT_FOUND || !HolderIsReceiverOrHiddenPrototype()); // 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 (!isolate()->IsInternallyUsedPropertyName(name()) && !receiver->map()->is_extensible()) { return; } auto transition = Map::TransitionToDataProperty( handle(receiver->map(), isolate_), name_, value, attributes, store_mode); state_ = TRANSITION; transition_ = transition; if (receiver->IsJSGlobalObject()) { // Install a property cell. InternalizeName(); auto cell = JSGlobalObject::EnsurePropertyCell( Handle<JSGlobalObject>::cast(receiver), name()); DCHECK(cell->value()->IsTheHole()); transition_ = cell; } else if (!transition->is_dictionary_map()) { property_details_ = transition->GetLastDescriptorDetails(); has_property_ = true; } } void LookupIterator::ApplyTransitionToDataProperty() { DCHECK_EQ(TRANSITION, state_); Handle<JSObject> receiver = GetStoreTarget(); if (receiver->IsJSGlobalObject()) return; holder_ = receiver; holder_map_ = transition_map(); JSObject::MigrateToMap(receiver, holder_map_); ReloadPropertyInformation(); } 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 { PropertyNormalizationMode mode = holder->map()->is_prototype_map() ? KEEP_INOBJECT_PROPERTIES : CLEAR_INOBJECT_PROPERTIES; if (holder->HasFastProperties()) { JSObject::NormalizeProperties(Handle<JSObject>::cast(holder), mode, 0, "DeletingProperty"); holder_map_ = handle(holder->map(), isolate_); ReloadPropertyInformation(); } // TODO(verwaest): Get rid of the name_ argument. JSReceiver::DeleteNormalizedProperty(holder, name_, number_); if (holder->IsJSObject()) { JSObject::ReoptimizeIfPrototype(Handle<JSObject>::cast(holder)); } } } void LookupIterator::TransitionToAccessorProperty( AccessorComponent component, Handle<Object> accessor, PropertyAttributes attributes) { DCHECK(!accessor->IsNull()); // 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() && !receiver->map()->is_dictionary_map()) { holder_ = receiver; holder_map_ = Map::TransitionToAccessorProperty( handle(receiver->map(), isolate_), name_, component, accessor, attributes); JSObject::MigrateToMap(receiver, holder_map_); ReloadPropertyInformation(); if (!holder_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->get(component) == *accessor) { if (property_details().attributes() == attributes) return; } else { pair = AccessorPair::Copy(pair); pair->set(component, *accessor); } } else { pair = factory()->NewAccessorPair(); pair->set(component, *accessor); } 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(attributes, ACCESSOR_CONSTANT, 0, PropertyCellType::kMutable); if (IsElement()) { // TODO(verwaest): Move code into the element accessor. Handle<SeededNumberDictionary> dictionary = JSObject::NormalizeElements(receiver); // We unconditionally pass used_as_prototype=false here because the call // to RequireSlowElements takes care of the required IC clearing and // we don't want to walk the heap twice. dictionary = SeededNumberDictionary::Set(dictionary, index_, pair, details, false); 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); } } 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); } holder_map_ = handle(receiver->map(), isolate_); ReloadPropertyInformation(); } bool LookupIterator::HolderIsReceiverOrHiddenPrototype() const { DCHECK(has_property_ || state_ == INTERCEPTOR || state_ == JSPROXY); return InternalHolderIsReceiverOrHiddenPrototype(); } bool LookupIterator::InternalHolderIsReceiverOrHiddenPrototype() const { // Optimization that only works if configuration_ is not mutable. if (!check_prototype_chain()) return true; DisallowHeapAllocation no_gc; if (!receiver_->IsJSReceiver()) return false; Object* current = *receiver_; JSReceiver* holder = *holder_; // JSProxy do not occur as hidden prototypes. if (current->IsJSProxy()) { return JSReceiver::cast(current) == holder; } PrototypeIterator iter(isolate(), current, PrototypeIterator::START_AT_RECEIVER); do { if (iter.GetCurrent<JSReceiver>() == holder) return true; DCHECK(!current->IsJSProxy()); iter.Advance(); } while (!iter.IsAtEnd(PrototypeIterator::END_AT_NON_HIDDEN)); return false; } Handle<Object> LookupIterator::FetchValue() const { Object* result = NULL; if (IsElement()) { Handle<JSObject> holder = GetHolder<JSObject>(); // TODO(verwaest): Optimize. if (holder->IsStringObjectWithCharacterAt(index_)) { Handle<JSValue> js_value = Handle<JSValue>::cast(holder); Handle<String> string(String::cast(js_value->value())); return factory()->LookupSingleCharacterStringFromCode( String::Flatten(string)->Get(index_)); } ElementsAccessor* accessor = holder->GetElementsAccessor(); return accessor->Get(handle(holder->elements()), number_); } else if (holder_map_->IsJSGlobalObjectMap()) { Handle<JSObject> holder = GetHolder<JSObject>(); result = holder->global_dictionary()->ValueAt(number_); DCHECK(result->IsPropertyCell()); result = PropertyCell::cast(result)->value(); } else if (holder_map_->is_dictionary_map()) { result = holder_->property_dictionary()->ValueAt(number_); } else if (property_details_.type() == v8::internal::DATA) { 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_); } int LookupIterator::GetAccessorIndex() const { DCHECK(has_property_); DCHECK(!holder_map_->is_dictionary_map()); DCHECK_EQ(v8::internal::ACCESSOR_CONSTANT, property_details_.type()); return descriptor_number(); } int LookupIterator::GetConstantIndex() const { DCHECK(has_property_); DCHECK(!holder_map_->is_dictionary_map()); DCHECK_EQ(v8::internal::DATA_CONSTANT, property_details_.type()); DCHECK(!IsElement()); return descriptor_number(); } FieldIndex LookupIterator::GetFieldIndex() const { DCHECK(has_property_); DCHECK(!holder_map_->is_dictionary_map()); DCHECK_EQ(v8::internal::DATA, property_details_.type()); DCHECK(!IsElement()); int index = holder_map_->instance_descriptors()->GetFieldIndex(descriptor_number()); bool is_double = representation().IsDouble(); return FieldIndex::ForPropertyIndex(*holder_map_, index, is_double); } Handle<HeapType> LookupIterator::GetFieldType() const { DCHECK(has_property_); DCHECK(!holder_map_->is_dictionary_map()); DCHECK_EQ(v8::internal::DATA, property_details_.type()); return handle( holder_map_->instance_descriptors()->GetFieldType(descriptor_number()), isolate_); } Handle<PropertyCell> LookupIterator::GetPropertyCell() const { DCHECK(!IsElement()); Handle<JSObject> holder = GetHolder<JSObject>(); Handle<JSGlobalObject> global = Handle<JSGlobalObject>::cast(holder); Object* value = global->global_dictionary()->ValueAt(dictionary_entry()); DCHECK(value->IsPropertyCell()); return handle(PropertyCell::cast(value)); } 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) { 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->elements(), number_, *value); } else if (holder->IsJSGlobalObject()) { Handle<GlobalDictionary> property_dictionary = handle(JSObject::cast(*holder)->global_dictionary()); PropertyCell::UpdateCell(property_dictionary, dictionary_entry(), value, property_details_); } else if (holder_map_->is_dictionary_map()) { NameDictionary* property_dictionary = holder->property_dictionary(); property_dictionary->ValueAtPut(dictionary_entry(), *value); } else if (property_details_.type() == v8::internal::DATA) { JSObject::cast(*holder)->WriteToField(descriptor_number(), *value); } else { DCHECK_EQ(v8::internal::DATA_CONSTANT, property_details_.type()); } } bool LookupIterator::IsIntegerIndexedExotic(JSReceiver* holder) { DCHECK(exotic_index_state_ != ExoticIndexState::kNotExotic); if (exotic_index_state_ == ExoticIndexState::kExotic) return true; if (!InternalHolderIsReceiverOrHiddenPrototype()) { exotic_index_state_ = ExoticIndexState::kNotExotic; return false; } DCHECK(exotic_index_state_ == ExoticIndexState::kUninitialized); bool result = false; // Compute and cache result. if (IsElement()) { result = index_ >= JSTypedArray::cast(holder)->length_value(); } else if (name()->IsString()) { Handle<String> name_string = Handle<String>::cast(name()); if (name_string->length() != 0) { result = IsSpecialIndex(isolate_->unicode_cache(), *name_string); } } exotic_index_state_ = result ? ExoticIndexState::kExotic : ExoticIndexState::kNotExotic; return result; } void LookupIterator::InternalizeName() { if (name_->IsUniqueName()) return; name_ = factory()->InternalizeString(Handle<String>::cast(name_)); } bool LookupIterator::HasInterceptor(Map* map) const { if (IsElement()) return map->has_indexed_interceptor(); return map->has_named_interceptor(); } bool LookupIterator::SkipInterceptor(JSObject* holder) { auto info = GetInterceptor(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()->IsJSReceiver()) return NULL; JSReceiver* next = JSReceiver::cast(map->prototype()); DCHECK(!next->map()->IsJSGlobalObjectMap() || next->map()->is_hidden_prototype()); if (!check_prototype_chain() && !(check_hidden() && next->map()->is_hidden_prototype()) && // Always lookup behind the JSGlobalProxy into the JSGlobalObject, even // when not checking other hidden prototypes. !map->IsJSGlobalProxyMap()) { return NULL; } return next; } LookupIterator::State LookupIterator::LookupInHolder(Map* const map, JSReceiver* const holder) { STATIC_ASSERT(INTERCEPTOR == BEFORE_PROPERTY); DisallowHeapAllocation no_gc; if (interceptor_state_ == InterceptorState::kProcessNonMasking) { return LookupNonMaskingInterceptorInHolder(map, holder); } switch (state_) { case NOT_FOUND: if (map->IsJSProxyMap()) { // Do not leak private property names. if (IsElement() || !name_->IsPrivate()) return JSPROXY; } if (map->is_access_check_needed() && (IsElement() || !isolate_->IsInternallyUsedPropertyName(name_))) { return ACCESS_CHECK; } // Fall through. case ACCESS_CHECK: if (exotic_index_state_ != ExoticIndexState::kNotExotic && holder->IsJSTypedArray() && IsIntegerIndexedExotic(holder)) { return INTEGER_INDEXED_EXOTIC; } if (check_interceptor() && HasInterceptor(map) && !SkipInterceptor(JSObject::cast(holder))) { // Do not leak private property names. if (!name_.is_null() && name_->IsPrivate()) return NOT_FOUND; return INTERCEPTOR; } // Fall through. case INTERCEPTOR: if (IsElement()) { // TODO(verwaest): Optimize. if (holder->IsStringObjectWithCharacterAt(index_)) { PropertyAttributes attributes = static_cast<PropertyAttributes>(READ_ONLY | DONT_DELETE); property_details_ = PropertyDetails(attributes, v8::internal::DATA, 0, PropertyCellType::kNoCell); } else { JSObject* js_object = JSObject::cast(holder); if (js_object->elements() == isolate()->heap()->empty_fixed_array()) { return NOT_FOUND; } ElementsAccessor* accessor = js_object->GetElementsAccessor(); FixedArrayBase* backing_store = js_object->elements(); number_ = accessor->GetEntryForIndex(js_object, backing_store, index_); if (number_ == kMaxUInt32) return NOT_FOUND; property_details_ = accessor->GetDetails(backing_store, number_); } } else if (!map->is_dictionary_map()) { DescriptorArray* descriptors = map->instance_descriptors(); int number = descriptors->SearchWithCache(*name_, map); if (number == DescriptorArray::kNotFound) return NOT_FOUND; number_ = static_cast<uint32_t>(number); property_details_ = descriptors->GetDetails(number_); } else if (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()) return NOT_FOUND; property_details_ = cell->property_details(); } else { NameDictionary* dict = holder->property_dictionary(); int number = dict->FindEntry(name_); if (number == NameDictionary::kNotFound) return NOT_FOUND; 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; } case ACCESSOR: case DATA: return NOT_FOUND; case INTEGER_INDEXED_EXOTIC: case JSPROXY: case TRANSITION: UNREACHABLE(); } UNREACHABLE(); return state_; } LookupIterator::State LookupIterator::LookupNonMaskingInterceptorInHolder( Map* const map, JSReceiver* const holder) { switch (state_) { case NOT_FOUND: if (check_interceptor() && HasInterceptor(map) && !SkipInterceptor(JSObject::cast(holder))) { return INTERCEPTOR; } // Fall through. default: return NOT_FOUND; } UNREACHABLE(); return state_; } } // namespace internal } // namespace v8