3fbb45216d
Now LookupIterator follows the same pattern of prepare transition, apply transition and write value when adding new properties to dictionary objects. JSGlobalObject case: * Prepare transition phase ensures that there is a "transition" property cell prepared for receiving a value. * Apply transition phase does nothing. * Prepare for data property phase ensures that the existing property cell can receive the value. * Write value phase writes value directly to the current property cell. JSObject case: * Prepare transition phase prepares the object for receiving a data value (which could switch an object to dictionary mode). * Apply transition phase migrates object to a transition map. If the map happened to be a dictionary mode object's map then an uninitialized entry added to the properties dictionary. * Prepare for data property phase does nothing. * Write value phase just puts value to the properties dictionary. BUG=chromium:576312 Review-Url: https://codereview.chromium.org/2127583002 Cr-Commit-Position: refs/heads/master@{#37585}
837 lines
29 KiB
C++
837 lines
29 KiB
C++
// 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->instance_type() <= LAST_SPECIAL_RECEIVER_TYPE) {
|
|
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->GetRootMap(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()->has_instance_symbol()) {
|
|
if (!isolate_->IsHasInstanceLookupChainIntact()) return;
|
|
isolate_->InvalidateHasInstanceProtector();
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
Handle<Map> old_map(holder->map(), isolate_);
|
|
Handle<Map> new_map =
|
|
Map::PrepareForDataProperty(old_map, descriptor_number(), 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);
|
|
new_map = Map::PrepareForDataProperty(new_map, descriptor_number(), value);
|
|
JSObject::MigrateToMap(holder, new_map);
|
|
ReloadPropertyInformation<false>();
|
|
} else {
|
|
PropertyDetails details(attributes, v8::internal::DATA, 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);
|
|
|
|
#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;
|
|
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(attributes, i::DATA, 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(attributes, i::DATA, 0, PropertyCellType::kNoCell);
|
|
transition_ = map;
|
|
}
|
|
return;
|
|
}
|
|
|
|
Handle<Map> transition =
|
|
Map::TransitionToDataProperty(map, name_, value, attributes, store_mode);
|
|
state_ = TRANSITION;
|
|
transition_ = transition;
|
|
|
|
if (!transition->is_dictionary_map()) {
|
|
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() && !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(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);
|
|
}
|
|
|
|
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_.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_->HasFastProperties());
|
|
DCHECK_EQ(v8::internal::ACCESSOR_CONSTANT, property_details_.type());
|
|
return descriptor_number();
|
|
}
|
|
|
|
|
|
int LookupIterator::GetConstantIndex() const {
|
|
DCHECK(has_property_);
|
|
DCHECK(holder_->HasFastProperties());
|
|
DCHECK_EQ(v8::internal::DATA_CONSTANT, property_details_.type());
|
|
DCHECK(!IsElement());
|
|
return descriptor_number();
|
|
}
|
|
|
|
|
|
FieldIndex LookupIterator::GetFieldIndex() const {
|
|
DCHECK(has_property_);
|
|
DCHECK(holder_->HasFastProperties());
|
|
DCHECK_EQ(v8::internal::DATA, property_details_.type());
|
|
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(v8::internal::DATA, property_details_.type());
|
|
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) {
|
|
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_.type() == v8::internal::DATA) {
|
|
JSObject::cast(*holder)->WriteToField(descriptor_number(),
|
|
property_details_, *value);
|
|
} else {
|
|
DCHECK_EQ(v8::internal::DATA_CONSTANT, property_details_.type());
|
|
}
|
|
} 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(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>();
|
|
}
|
|
|
|
} // namespace internal
|
|
} // namespace v8
|