blob: bb35938d5d8153b9e599867ce4e1790a542541f5 [file] [log] [blame]
// 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, Handle<JSReceiver> holder, Configuration configuration) {
uint32_t index = 0;
if (key->ToArrayIndex(&index)) {
*success = true;
return LookupIterator(isolate, receiver, index, holder, 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, 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<Object> receiver,
Handle<Object> 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> 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);
}
// static
LookupIterator LookupIterator::ForTransitionHandler(
Isolate* isolate, Handle<Object> receiver, Handle<Name> name,
Handle<Object> value, MaybeHandle<Object> handler,
Handle<Map> transition_map) {
if (handler.is_null()) return LookupIterator(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;
}
LookupIterator it(isolate, receiver, name, transition_map, details,
has_property);
if (!transition_map->is_dictionary_map()) {
int descriptor_number = transition_map->LastAdded();
Handle<Map> new_map = Map::PrepareForDataProperty(
transition_map, descriptor_number, 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<Object> receiver,
Handle<Name> name, Handle<Map> 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<uint32_t>(DescriptorArray::kNotFound)) {
holder_ = initial_holder_;
}
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_ = static_cast<uint32_t>(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, nullptr, 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());
}
namespace {
bool IsTypedArrayFunctionInAnyContext(Isolate* isolate, JSReceiver* holder) {
static uint32_t context_slots[] = {
#define TYPED_ARRAY_CONTEXT_SLOTS(Type, type, TYPE, ctype, size) \
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;
if (*name_ == heap()->constructor_string()) {
if (!isolate_->IsArraySpeciesLookupChainIntact()) return;
// Setting the constructor property could change an instance's @@species
if (holder_->IsJSArray() || holder_->IsJSTypedArray()) {
isolate_->CountUsage(
v8::Isolate::UseCounterFeature::kArrayInstanceConstructorModified);
isolate_->InvalidateArraySpeciesProtector();
} else if (holder_->map()->is_prototype_map()) {
DisallowHeapAllocation no_gc;
// Setting the constructor of Array.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) ||
isolate_->IsInAnyContext(holder_->map()->prototype(),
Context::TYPED_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 or TypedArray
// constructor invalidates the species protector
if (isolate_->IsInAnyContext(*holder_, Context::ARRAY_FUNCTION_INDEX) ||
IsTypedArrayFunctionInAnyContext(isolate_, *holder_)) {
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 (IsHoleyOrDictionaryElementsKind(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 (IsSmiOrObjectElementsKind(to)) {
JSObject::EnsureWritableFastElements(holder);
}
return;
}
if (holder->IsJSGlobalObject()) {
Handle<GlobalDictionary> dictionary(
JSGlobalObject::cast(*holder)->global_dictionary());
Handle<PropertyCell> cell(dictionary->CellAt(dictionary_entry()));
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 (constness() != new_constness || 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>();
}
if (!IsElement() && !holder->HasFastProperties()) {
PropertyDetails details(kData, attributes, PropertyCellType::kMutable);
if (holder->IsJSGlobalObject()) {
Handle<GlobalDictionary> dictionary(
JSGlobalObject::cast(*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_GT(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.
// Returns true if a new transition has been created, or false if an existing
// transition was followed.
bool LookupIterator::PrepareTransitionToDataProperty(
Handle<JSObject> receiver, Handle<Object> value,
PropertyAttributes attributes, Object::StoreFromKeyed store_mode) {
DCHECK(receiver.is_identical_to(GetStoreTarget()));
if (state_ == TRANSITION) return false;
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_);
#if !defined(COBALT_ARRAY_BUFFER_COLLISION_WORKAROUND)
DCHECK(state_ == NOT_FOUND || !HolderIsReceiverOrHiddenPrototype());
#endif
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_);
#if !defined(COBALT_ARRAY_BUFFER_COLLISION_WORKAROUND)
DCHECK(cell->value()->IsTheHole(isolate_));
#endif
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(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 false;
}
bool created_new_map;
Handle<Map> transition = Map::TransitionToDataProperty(
map, name_, value, attributes, kDefaultFieldConstness, store_mode,
&created_new_map);
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;
}
return created_new_map;
}
void LookupIterator::ApplyTransitionToDataProperty(Handle<JSObject> receiver) {
DCHECK_EQ(TRANSITION, state_);
DCHECK(receiver.is_identical_to(GetStoreTarget()));
holder_ = receiver;
if (receiver->IsJSGlobalObject()) {
JSObject::InvalidatePrototypeChains(receiver->map());
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;
if (receiver->map()->is_prototype_map()) {
JSObject::InvalidatePrototypeChains(receiver->map());
}
dictionary = NameDictionary::Add(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<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
? RuntimeCallCounterId::kPrototypeObject_DeleteProperty
: RuntimeCallCounterId::kObject_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>();
}
JSReceiver::DeleteNormalizedProperty(holder, 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, PropertyCellType::kMutable);
if (IsElement()) {
// TODO(verwaest): Move code into the element accessor.
isolate_->CountUsage(v8::Isolate::kIndexAccessor);
Handle<NumberDictionary> dictionary = JSObject::NormalizeElements(receiver);
dictionary =
NumberDictionary::Set(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, heap()->the_hole_value());
}
FixedArray::cast(receiver->elements())->set(1, *dictionary);
} else {
receiver->set_elements(*dictionary);
}
ReloadPropertyInformation<true>();
} 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<false>();
}
}
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<JSReceiver>() == object) return true;
iter.Advance();
}
return false;
}
Handle<Object> LookupIterator::FetchValue() const {
Object* result = nullptr;
if (IsElement()) {
Handle<JSObject> holder = GetHolder<JSObject>();
ElementsAccessor* accessor = holder->GetElementsAccessor();
return accessor->Get(holder, number_);
} else if (holder_->IsJSGlobalObject()) {
Handle<JSGlobalObject> holder = GetHolder<JSGlobalObject>();
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<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());
return FieldIndex::ForDescriptor(holder_->map(), descriptor_number());
}
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>();
return handle(holder->global_dictionary()->CellAt(dictionary_entry()),
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 =
JSGlobalObject::cast(*holder)->global_dictionary();
dictionary->CellAt(dictionary_entry())->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);
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;
// 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 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<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 =
JSGlobalObject::cast(holder)->global_dictionary();
int number = dict->FindEntry(name_);
if (number == GlobalDictionary::kNotFound) return NOT_FOUND;
number_ = static_cast<uint32_t>(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<is_element>(map, holder);
case ACCESSOR:
case DATA:
return NOT_FOUND;
case INTEGER_INDEXED_EXOTIC:
case JSPROXY:
case TRANSITION:
UNREACHABLE();
}
UNREACHABLE();
}
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();
}
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