// Copyright 2013 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/keys.h" #include "src/api-arguments-inl.h" #include "src/elements-inl.h" #include "src/handles-inl.h" #include "src/heap/factory.h" #include "src/identity-map.h" #include "src/isolate-inl.h" #include "src/objects-inl.h" #include "src/objects/api-callbacks.h" #include "src/objects/hash-table-inl.h" #include "src/objects/module-inl.h" #include "src/property-descriptor.h" #include "src/prototype.h" namespace v8 { namespace internal { namespace { static bool ContainsOnlyValidKeys(Handle array) { int len = array->length(); for (int i = 0; i < len; i++) { Object* e = array->get(i); if (!(e->IsName() || e->IsNumber())) return false; } return true; } } // namespace // static MaybeHandle KeyAccumulator::GetKeys( Handle object, KeyCollectionMode mode, PropertyFilter filter, GetKeysConversion keys_conversion, bool is_for_in, bool skip_indices) { Isolate* isolate = object->GetIsolate(); FastKeyAccumulator accumulator(isolate, object, mode, filter, is_for_in, skip_indices); return accumulator.GetKeys(keys_conversion); } Handle KeyAccumulator::GetKeys(GetKeysConversion convert) { if (keys_.is_null()) { return isolate_->factory()->empty_fixed_array(); } if (mode_ == KeyCollectionMode::kOwnOnly && keys_->map() == ReadOnlyRoots(isolate_).fixed_array_map()) { return Handle::cast(keys_); } USE(ContainsOnlyValidKeys); Handle result = OrderedHashSet::ConvertToKeysArray(isolate(), keys(), convert); DCHECK(ContainsOnlyValidKeys(result)); return result; } Handle KeyAccumulator::keys() { return Handle::cast(keys_); } void KeyAccumulator::AddKey(Object* key, AddKeyConversion convert) { AddKey(handle(key, isolate_), convert); } void KeyAccumulator::AddKey(Handle key, AddKeyConversion convert) { if (key->IsSymbol()) { if (filter_ & SKIP_SYMBOLS) return; if (Handle::cast(key)->is_private()) return; } else if (filter_ & SKIP_STRINGS) { return; } if (IsShadowed(key)) return; if (keys_.is_null()) { keys_ = OrderedHashSet::Allocate(isolate_, 16); } uint32_t index; if (convert == CONVERT_TO_ARRAY_INDEX && key->IsString() && Handle::cast(key)->AsArrayIndex(&index)) { key = isolate_->factory()->NewNumberFromUint(index); } Handle new_set = OrderedHashSet::Add(isolate(), keys(), key); if (*new_set != *keys_) { // The keys_ Set is converted directly to a FixedArray in GetKeys which can // be left-trimmer. Hence the previous Set should not keep a pointer to the // new one. keys_->set(OrderedHashTableBase::kNextTableIndex, Smi::kZero); keys_ = new_set; } } void KeyAccumulator::AddKeys(Handle array, AddKeyConversion convert) { int add_length = array->length(); for (int i = 0; i < add_length; i++) { Handle current(array->get(i), isolate_); AddKey(current, convert); } } void KeyAccumulator::AddKeys(Handle array_like, AddKeyConversion convert) { DCHECK(array_like->IsJSArray() || array_like->HasSloppyArgumentsElements()); ElementsAccessor* accessor = array_like->GetElementsAccessor(); accessor->AddElementsToKeyAccumulator(array_like, this, convert); } MaybeHandle FilterProxyKeys(KeyAccumulator* accumulator, Handle owner, Handle keys, PropertyFilter filter) { if (filter == ALL_PROPERTIES) { // Nothing to do. return keys; } Isolate* isolate = accumulator->isolate(); int store_position = 0; for (int i = 0; i < keys->length(); ++i) { Handle key(Name::cast(keys->get(i)), isolate); if (key->FilterKey(filter)) continue; // Skip this key. if (filter & ONLY_ENUMERABLE) { PropertyDescriptor desc; Maybe found = JSProxy::GetOwnPropertyDescriptor(isolate, owner, key, &desc); MAYBE_RETURN(found, MaybeHandle()); if (!found.FromJust()) continue; if (!desc.enumerable()) { accumulator->AddShadowingKey(key); continue; } } // Keep this key. if (store_position != i) { keys->set(store_position, *key); } store_position++; } return FixedArray::ShrinkOrEmpty(isolate, keys, store_position); } // Returns "nothing" in case of exception, "true" on success. Maybe KeyAccumulator::AddKeysFromJSProxy(Handle proxy, Handle keys) { // Postpone the enumerable check for for-in to the ForInFilter step. if (!is_for_in_) { ASSIGN_RETURN_ON_EXCEPTION_VALUE( isolate_, keys, FilterProxyKeys(this, proxy, keys, filter_), Nothing()); if (mode_ == KeyCollectionMode::kOwnOnly) { // If we collect only the keys from a JSProxy do not sort or deduplicate. keys_ = keys; return Just(true); } } AddKeys(keys, is_for_in_ ? CONVERT_TO_ARRAY_INDEX : DO_NOT_CONVERT); return Just(true); } Maybe KeyAccumulator::CollectKeys(Handle receiver, Handle object) { // Proxies have no hidden prototype and we should not trigger the // [[GetPrototypeOf]] trap on the last iteration when using // AdvanceFollowingProxies. if (mode_ == KeyCollectionMode::kOwnOnly && object->IsJSProxy()) { MAYBE_RETURN(CollectOwnJSProxyKeys(receiver, Handle::cast(object)), Nothing()); return Just(true); } PrototypeIterator::WhereToEnd end = mode_ == KeyCollectionMode::kOwnOnly ? PrototypeIterator::END_AT_NON_HIDDEN : PrototypeIterator::END_AT_NULL; for (PrototypeIterator iter(isolate_, object, kStartAtReceiver, end); !iter.IsAtEnd();) { // Start the shadow checks only after the first prototype has added // shadowing keys. if (HasShadowingKeys()) skip_shadow_check_ = false; Handle current = PrototypeIterator::GetCurrent(iter); Maybe result = Just(false); // Dummy initialization. if (current->IsJSProxy()) { result = CollectOwnJSProxyKeys(receiver, Handle::cast(current)); } else { DCHECK(current->IsJSObject()); result = CollectOwnKeys(receiver, Handle::cast(current)); } MAYBE_RETURN(result, Nothing()); if (!result.FromJust()) break; // |false| means "stop iterating". // Iterate through proxies but ignore access checks for the ALL_CAN_READ // case on API objects for OWN_ONLY keys handled in CollectOwnKeys. if (!iter.AdvanceFollowingProxiesIgnoringAccessChecks()) { return Nothing(); } if (!last_non_empty_prototype_.is_null() && *last_non_empty_prototype_ == *current) { break; } } return Just(true); } bool KeyAccumulator::HasShadowingKeys() { return !shadowing_keys_.is_null(); } bool KeyAccumulator::IsShadowed(Handle key) { if (!HasShadowingKeys() || skip_shadow_check_) return false; return shadowing_keys_->Has(isolate_, key); } void KeyAccumulator::AddShadowingKey(Object* key) { if (mode_ == KeyCollectionMode::kOwnOnly) return; AddShadowingKey(handle(key, isolate_)); } void KeyAccumulator::AddShadowingKey(Handle key) { if (mode_ == KeyCollectionMode::kOwnOnly) return; if (shadowing_keys_.is_null()) { shadowing_keys_ = ObjectHashSet::New(isolate_, 16); } shadowing_keys_ = ObjectHashSet::Add(isolate(), shadowing_keys_, key); } namespace { void TrySettingEmptyEnumCache(JSReceiver* object) { Map* map = object->map(); DCHECK_EQ(kInvalidEnumCacheSentinel, map->EnumLength()); if (!map->OnlyHasSimpleProperties()) return; if (map->IsJSProxyMap()) return; if (map->NumberOfEnumerableProperties() > 0) return; DCHECK(object->IsJSObject()); map->SetEnumLength(0); } bool CheckAndInitalizeEmptyEnumCache(JSReceiver* object) { if (object->map()->EnumLength() == kInvalidEnumCacheSentinel) { TrySettingEmptyEnumCache(object); } if (object->map()->EnumLength() != 0) return false; DCHECK(object->IsJSObject()); return !JSObject::cast(object)->HasEnumerableElements(); } } // namespace void FastKeyAccumulator::Prepare() { DisallowHeapAllocation no_gc; // Directly go for the fast path for OWN_ONLY keys. if (mode_ == KeyCollectionMode::kOwnOnly) return; // Fully walk the prototype chain and find the last prototype with keys. is_receiver_simple_enum_ = false; has_empty_prototype_ = true; JSReceiver* last_prototype = nullptr; for (PrototypeIterator iter(isolate_, *receiver_); !iter.IsAtEnd(); iter.Advance()) { JSReceiver* current = iter.GetCurrent(); bool has_no_properties = CheckAndInitalizeEmptyEnumCache(current); if (has_no_properties) continue; last_prototype = current; has_empty_prototype_ = false; } if (has_empty_prototype_) { is_receiver_simple_enum_ = receiver_->map()->EnumLength() != kInvalidEnumCacheSentinel && !JSObject::cast(*receiver_)->HasEnumerableElements(); } else if (last_prototype != nullptr) { last_non_empty_prototype_ = handle(last_prototype, isolate_); } } namespace { Handle ReduceFixedArrayTo(Isolate* isolate, Handle array, int length) { DCHECK_LE(length, array->length()); if (array->length() == length) return array; return isolate->factory()->CopyFixedArrayUpTo(array, length); } // Initializes and directly returns the enume cache. Users of this function // have to make sure to never directly leak the enum cache. Handle GetFastEnumPropertyKeys(Isolate* isolate, Handle object) { Handle map(object->map(), isolate); Handle keys(map->instance_descriptors()->GetEnumCache()->keys(), isolate); // Check if the {map} has a valid enum length, which implies that it // must have a valid enum cache as well. int enum_length = map->EnumLength(); if (enum_length != kInvalidEnumCacheSentinel) { DCHECK(map->OnlyHasSimpleProperties()); DCHECK_LE(enum_length, keys->length()); DCHECK_EQ(enum_length, map->NumberOfEnumerableProperties()); isolate->counters()->enum_cache_hits()->Increment(); return ReduceFixedArrayTo(isolate, keys, enum_length); } // Determine the actual number of enumerable properties of the {map}. enum_length = map->NumberOfEnumerableProperties(); // Check if there's already a shared enum cache on the {map}s // DescriptorArray with sufficient number of entries. if (enum_length <= keys->length()) { if (map->OnlyHasSimpleProperties()) map->SetEnumLength(enum_length); isolate->counters()->enum_cache_hits()->Increment(); return ReduceFixedArrayTo(isolate, keys, enum_length); } Handle descriptors = Handle(map->instance_descriptors(), isolate); isolate->counters()->enum_cache_misses()->Increment(); int nod = map->NumberOfOwnDescriptors(); // Create the keys array. int index = 0; bool fields_only = true; keys = isolate->factory()->NewFixedArray(enum_length); for (int i = 0; i < nod; i++) { DisallowHeapAllocation no_gc; PropertyDetails details = descriptors->GetDetails(i); if (details.IsDontEnum()) continue; Object* key = descriptors->GetKey(i); if (key->IsSymbol()) continue; keys->set(index, key); if (details.location() != kField) fields_only = false; index++; } DCHECK_EQ(index, keys->length()); // Optionally also create the indices array. Handle indices = isolate->factory()->empty_fixed_array(); if (fields_only) { indices = isolate->factory()->NewFixedArray(enum_length); index = 0; for (int i = 0; i < nod; i++) { DisallowHeapAllocation no_gc; PropertyDetails details = descriptors->GetDetails(i); if (details.IsDontEnum()) continue; Object* key = descriptors->GetKey(i); if (key->IsSymbol()) continue; DCHECK_EQ(kData, details.kind()); DCHECK_EQ(kField, details.location()); FieldIndex field_index = FieldIndex::ForDescriptor(*map, i); indices->set(index, Smi::FromInt(field_index.GetLoadByFieldIndex())); index++; } DCHECK_EQ(index, indices->length()); } DescriptorArray::SetEnumCache(descriptors, isolate, keys, indices); if (map->OnlyHasSimpleProperties()) map->SetEnumLength(enum_length); return keys; } template MaybeHandle GetOwnKeysWithElements(Isolate* isolate, Handle object, GetKeysConversion convert, bool skip_indices) { Handle keys; ElementsAccessor* accessor = object->GetElementsAccessor(); if (fast_properties) { keys = GetFastEnumPropertyKeys(isolate, object); } else { // TODO(cbruni): preallocate big enough array to also hold elements. keys = KeyAccumulator::GetOwnEnumPropertyKeys(isolate, object); } MaybeHandle result; if (skip_indices) { result = keys; } else { result = accessor->PrependElementIndices(object, keys, convert, ONLY_ENUMERABLE); } if (FLAG_trace_for_in_enumerate) { PrintF("| strings=%d symbols=0 elements=%u || prototypes>=1 ||\n", keys->length(), result.ToHandleChecked()->length() - keys->length()); } return result; } } // namespace MaybeHandle FastKeyAccumulator::GetKeys( GetKeysConversion keys_conversion) { if (filter_ == ENUMERABLE_STRINGS) { Handle keys; if (GetKeysFast(keys_conversion).ToHandle(&keys)) { return keys; } if (isolate_->has_pending_exception()) return MaybeHandle(); } return GetKeysSlow(keys_conversion); } MaybeHandle FastKeyAccumulator::GetKeysFast( GetKeysConversion keys_conversion) { bool own_only = has_empty_prototype_ || mode_ == KeyCollectionMode::kOwnOnly; Map* map = receiver_->map(); if (!own_only || map->IsCustomElementsReceiverMap()) { return MaybeHandle(); } // From this point on we are certain to only collect own keys. DCHECK(receiver_->IsJSObject()); Handle object = Handle::cast(receiver_); // Do not try to use the enum-cache for dict-mode objects. if (map->is_dictionary_map()) { return GetOwnKeysWithElements(isolate_, object, keys_conversion, skip_indices_); } int enum_length = receiver_->map()->EnumLength(); if (enum_length == kInvalidEnumCacheSentinel) { Handle keys; // Try initializing the enum cache and return own properties. if (GetOwnKeysWithUninitializedEnumCache().ToHandle(&keys)) { if (FLAG_trace_for_in_enumerate) { PrintF("| strings=%d symbols=0 elements=0 || prototypes>=1 ||\n", keys->length()); } is_receiver_simple_enum_ = object->map()->EnumLength() != kInvalidEnumCacheSentinel; return keys; } } // The properties-only case failed because there were probably elements on the // receiver. return GetOwnKeysWithElements(isolate_, object, keys_conversion, skip_indices_); } MaybeHandle FastKeyAccumulator::GetOwnKeysWithUninitializedEnumCache() { Handle object = Handle::cast(receiver_); // Uninitalized enum cache Map* map = object->map(); if (object->elements()->length() != 0) { // Assume that there are elements. return MaybeHandle(); } int number_of_own_descriptors = map->NumberOfOwnDescriptors(); if (number_of_own_descriptors == 0) { map->SetEnumLength(0); return isolate_->factory()->empty_fixed_array(); } // We have no elements but possibly enumerable property keys, hence we can // directly initialize the enum cache. Handle keys = GetFastEnumPropertyKeys(isolate_, object); if (is_for_in_) return keys; // Do not leak the enum cache as it might end up as an elements backing store. return isolate_->factory()->CopyFixedArray(keys); } MaybeHandle FastKeyAccumulator::GetKeysSlow( GetKeysConversion keys_conversion) { KeyAccumulator accumulator(isolate_, mode_, filter_); accumulator.set_is_for_in(is_for_in_); accumulator.set_skip_indices(skip_indices_); accumulator.set_last_non_empty_prototype(last_non_empty_prototype_); MAYBE_RETURN(accumulator.CollectKeys(receiver_, receiver_), MaybeHandle()); return accumulator.GetKeys(keys_conversion); } namespace { enum IndexedOrNamed { kIndexed, kNamed }; void FilterForEnumerableProperties(Handle receiver, Handle object, Handle interceptor, KeyAccumulator* accumulator, Handle result, IndexedOrNamed type) { DCHECK(result->IsJSArray() || result->HasSloppyArgumentsElements()); ElementsAccessor* accessor = result->GetElementsAccessor(); uint32_t length = accessor->GetCapacity(*result, result->elements()); for (uint32_t i = 0; i < length; i++) { if (!accessor->HasEntry(*result, i)) continue; // args are invalid after args.Call(), create a new one in every iteration. PropertyCallbackArguments args(accumulator->isolate(), interceptor->data(), *receiver, *object, kDontThrow); Handle element = accessor->Get(result, i); Handle attributes; if (type == kIndexed) { uint32_t number; CHECK(element->ToUint32(&number)); attributes = args.CallIndexedQuery(interceptor, number); } else { CHECK(element->IsName()); attributes = args.CallNamedQuery(interceptor, Handle::cast(element)); } if (!attributes.is_null()) { int32_t value; CHECK(attributes->ToInt32(&value)); if ((value & DONT_ENUM) == 0) { accumulator->AddKey(element, DO_NOT_CONVERT); } } } } // Returns |true| on success, |nothing| on exception. Maybe CollectInterceptorKeysInternal(Handle receiver, Handle object, Handle interceptor, KeyAccumulator* accumulator, IndexedOrNamed type) { Isolate* isolate = accumulator->isolate(); PropertyCallbackArguments enum_args(isolate, interceptor->data(), *receiver, *object, kDontThrow); Handle result; if (!interceptor->enumerator()->IsUndefined(isolate)) { if (type == kIndexed) { result = enum_args.CallIndexedEnumerator(interceptor); } else { DCHECK_EQ(type, kNamed); result = enum_args.CallNamedEnumerator(interceptor); } } RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, Nothing()); if (result.is_null()) return Just(true); if ((accumulator->filter() & ONLY_ENUMERABLE) && !interceptor->query()->IsUndefined(isolate)) { FilterForEnumerableProperties(receiver, object, interceptor, accumulator, result, type); } else { accumulator->AddKeys( result, type == kIndexed ? CONVERT_TO_ARRAY_INDEX : DO_NOT_CONVERT); } return Just(true); } Maybe CollectInterceptorKeys(Handle receiver, Handle object, KeyAccumulator* accumulator, IndexedOrNamed type) { Isolate* isolate = accumulator->isolate(); if (type == kIndexed) { if (!object->HasIndexedInterceptor()) return Just(true); } else { if (!object->HasNamedInterceptor()) return Just(true); } Handle interceptor(type == kIndexed ? object->GetIndexedInterceptor() : object->GetNamedInterceptor(), isolate); if ((accumulator->filter() & ONLY_ALL_CAN_READ) && !interceptor->all_can_read()) { return Just(true); } return CollectInterceptorKeysInternal(receiver, object, interceptor, accumulator, type); } } // namespace Maybe KeyAccumulator::CollectOwnElementIndices( Handle receiver, Handle object) { if (filter_ & SKIP_STRINGS || skip_indices_) return Just(true); ElementsAccessor* accessor = object->GetElementsAccessor(); accessor->CollectElementIndices(object, this); return CollectInterceptorKeys(receiver, object, this, kIndexed); } namespace { template int CollectOwnPropertyNamesInternal(Handle object, KeyAccumulator* keys, Handle descs, int start_index, int limit) { int first_skipped = -1; PropertyFilter filter = keys->filter(); KeyCollectionMode mode = keys->mode(); for (int i = start_index; i < limit; i++) { bool is_shadowing_key = false; PropertyDetails details = descs->GetDetails(i); if ((details.attributes() & filter) != 0) { if (mode == KeyCollectionMode::kIncludePrototypes) { is_shadowing_key = true; } else { continue; } } if (filter & ONLY_ALL_CAN_READ) { if (details.kind() != kAccessor) continue; Object* accessors = descs->GetStrongValue(i); if (!accessors->IsAccessorInfo()) continue; if (!AccessorInfo::cast(accessors)->all_can_read()) continue; } Name* key = descs->GetKey(i); if (skip_symbols == key->IsSymbol()) { if (first_skipped == -1) first_skipped = i; continue; } if (key->FilterKey(keys->filter())) continue; if (is_shadowing_key) { keys->AddShadowingKey(key); } else { keys->AddKey(key, DO_NOT_CONVERT); } } return first_skipped; } template Handle GetOwnEnumPropertyDictionaryKeys(Isolate* isolate, KeyCollectionMode mode, KeyAccumulator* accumulator, Handle object, T* raw_dictionary) { Handle dictionary(raw_dictionary, isolate); if (dictionary->NumberOfElements() == 0) { return isolate->factory()->empty_fixed_array(); } int length = dictionary->NumberOfEnumerableProperties(); Handle storage = isolate->factory()->NewFixedArray(length); T::CopyEnumKeysTo(isolate, dictionary, storage, mode, accumulator); return storage; } } // namespace Maybe KeyAccumulator::CollectOwnPropertyNames(Handle receiver, Handle object) { if (filter_ == ENUMERABLE_STRINGS) { Handle enum_keys; if (object->HasFastProperties()) { enum_keys = KeyAccumulator::GetOwnEnumPropertyKeys(isolate_, object); // If the number of properties equals the length of enumerable properties // we do not have to filter out non-enumerable ones Map* map = object->map(); int nof_descriptors = map->NumberOfOwnDescriptors(); if (enum_keys->length() != nof_descriptors) { Handle descs = Handle(map->instance_descriptors(), isolate_); for (int i = 0; i < nof_descriptors; i++) { PropertyDetails details = descs->GetDetails(i); if (!details.IsDontEnum()) continue; Object* key = descs->GetKey(i); this->AddShadowingKey(key); } } } else if (object->IsJSGlobalObject()) { enum_keys = GetOwnEnumPropertyDictionaryKeys( isolate_, mode_, this, object, JSGlobalObject::cast(*object)->global_dictionary()); } else { enum_keys = GetOwnEnumPropertyDictionaryKeys( isolate_, mode_, this, object, object->property_dictionary()); } if (object->IsJSModuleNamespace()) { // Simulate [[GetOwnProperty]] for establishing enumerability, which // throws for uninitialized exports. for (int i = 0, n = enum_keys->length(); i < n; ++i) { Handle key(String::cast(enum_keys->get(i)), isolate_); if (Handle::cast(object) ->GetExport(isolate(), key) .is_null()) { return Nothing(); } } } AddKeys(enum_keys, DO_NOT_CONVERT); } else { if (object->HasFastProperties()) { int limit = object->map()->NumberOfOwnDescriptors(); Handle descs(object->map()->instance_descriptors(), isolate_); // First collect the strings, int first_symbol = CollectOwnPropertyNamesInternal(object, this, descs, 0, limit); // then the symbols. if (first_symbol != -1) { CollectOwnPropertyNamesInternal(object, this, descs, first_symbol, limit); } } else if (object->IsJSGlobalObject()) { GlobalDictionary::CollectKeysTo( handle(JSGlobalObject::cast(*object)->global_dictionary(), isolate_), this); } else { NameDictionary::CollectKeysTo( handle(object->property_dictionary(), isolate_), this); } } // Add the property keys from the interceptor. return CollectInterceptorKeys(receiver, object, this, kNamed); } Maybe KeyAccumulator::CollectAccessCheckInterceptorKeys( Handle access_check_info, Handle receiver, Handle object) { if (!skip_indices_) { MAYBE_RETURN((CollectInterceptorKeysInternal( receiver, object, handle(InterceptorInfo::cast( access_check_info->indexed_interceptor()), isolate_), this, kIndexed)), Nothing()); } MAYBE_RETURN( (CollectInterceptorKeysInternal( receiver, object, handle(InterceptorInfo::cast(access_check_info->named_interceptor()), isolate_), this, kNamed)), Nothing()); return Just(true); } // Returns |true| on success, |false| if prototype walking should be stopped, // |nothing| if an exception was thrown. Maybe KeyAccumulator::CollectOwnKeys(Handle receiver, Handle object) { // Check access rights if required. if (object->IsAccessCheckNeeded() && !isolate_->MayAccess(handle(isolate_->context(), isolate_), object)) { // The cross-origin spec says that [[Enumerate]] shall return an empty // iterator when it doesn't have access... if (mode_ == KeyCollectionMode::kIncludePrototypes) { return Just(false); } // ...whereas [[OwnPropertyKeys]] shall return whitelisted properties. DCHECK_EQ(KeyCollectionMode::kOwnOnly, mode_); Handle access_check_info; { DisallowHeapAllocation no_gc; AccessCheckInfo* maybe_info = AccessCheckInfo::Get(isolate_, object); if (maybe_info) access_check_info = handle(maybe_info, isolate_); } // We always have both kinds of interceptors or none. if (!access_check_info.is_null() && access_check_info->named_interceptor()) { MAYBE_RETURN(CollectAccessCheckInterceptorKeys(access_check_info, receiver, object), Nothing()); return Just(false); } filter_ = static_cast(filter_ | ONLY_ALL_CAN_READ); } MAYBE_RETURN(CollectOwnElementIndices(receiver, object), Nothing()); MAYBE_RETURN(CollectOwnPropertyNames(receiver, object), Nothing()); return Just(true); } // static Handle KeyAccumulator::GetOwnEnumPropertyKeys( Isolate* isolate, Handle object) { if (object->HasFastProperties()) { return GetFastEnumPropertyKeys(isolate, object); } else if (object->IsJSGlobalObject()) { return GetOwnEnumPropertyDictionaryKeys( isolate, KeyCollectionMode::kOwnOnly, nullptr, object, JSGlobalObject::cast(*object)->global_dictionary()); } else { return GetOwnEnumPropertyDictionaryKeys( isolate, KeyCollectionMode::kOwnOnly, nullptr, object, object->property_dictionary()); } } namespace { class NameComparator { public: explicit NameComparator(Isolate* isolate) : isolate_(isolate) {} bool operator()(uint32_t hash1, uint32_t hash2, const Handle& key1, const Handle& key2) const { return Name::Equals(isolate_, key1, key2); } private: Isolate* isolate_; }; } // namespace // ES6 9.5.12 // Returns |true| on success, |nothing| in case of exception. Maybe KeyAccumulator::CollectOwnJSProxyKeys(Handle receiver, Handle proxy) { STACK_CHECK(isolate_, Nothing()); // 1. Let handler be the value of the [[ProxyHandler]] internal slot of O. Handle handler(proxy->handler(), isolate_); // 2. If handler is null, throw a TypeError exception. // 3. Assert: Type(handler) is Object. if (proxy->IsRevoked()) { isolate_->Throw(*isolate_->factory()->NewTypeError( MessageTemplate::kProxyRevoked, isolate_->factory()->ownKeys_string())); return Nothing(); } // 4. Let target be the value of the [[ProxyTarget]] internal slot of O. Handle target(JSReceiver::cast(proxy->target()), isolate_); // 5. Let trap be ? GetMethod(handler, "ownKeys"). Handle trap; ASSIGN_RETURN_ON_EXCEPTION_VALUE( isolate_, trap, Object::GetMethod(Handle::cast(handler), isolate_->factory()->ownKeys_string()), Nothing()); // 6. If trap is undefined, then if (trap->IsUndefined(isolate_)) { // 6a. Return target.[[OwnPropertyKeys]](). return CollectOwnJSProxyTargetKeys(proxy, target); } // 7. Let trapResultArray be Call(trap, handler, «target»). Handle trap_result_array; Handle args[] = {target}; ASSIGN_RETURN_ON_EXCEPTION_VALUE( isolate_, trap_result_array, Execution::Call(isolate_, trap, handler, arraysize(args), args), Nothing()); // 8. Let trapResult be ? CreateListFromArrayLike(trapResultArray, // «String, Symbol»). Handle trap_result; ASSIGN_RETURN_ON_EXCEPTION_VALUE( isolate_, trap_result, Object::CreateListFromArrayLike(isolate_, trap_result_array, ElementTypes::kStringAndSymbol), Nothing()); // 9. Let extensibleTarget be ? IsExtensible(target). Maybe maybe_extensible = JSReceiver::IsExtensible(target); MAYBE_RETURN(maybe_extensible, Nothing()); bool extensible_target = maybe_extensible.FromJust(); // 10. Let targetKeys be ? target.[[OwnPropertyKeys]](). Handle target_keys; ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate_, target_keys, JSReceiver::OwnPropertyKeys(target), Nothing()); // 11. (Assert) // 12. Let targetConfigurableKeys be an empty List. // To save memory, we're re-using target_keys and will modify it in-place. Handle target_configurable_keys = target_keys; // 13. Let targetNonconfigurableKeys be an empty List. Handle target_nonconfigurable_keys = isolate_->factory()->NewFixedArray(target_keys->length()); int nonconfigurable_keys_length = 0; // 14. Repeat, for each element key of targetKeys: for (int i = 0; i < target_keys->length(); ++i) { // 14a. Let desc be ? target.[[GetOwnProperty]](key). PropertyDescriptor desc; Maybe found = JSReceiver::GetOwnPropertyDescriptor( isolate_, target, handle(target_keys->get(i), isolate_), &desc); MAYBE_RETURN(found, Nothing()); // 14b. If desc is not undefined and desc.[[Configurable]] is false, then if (found.FromJust() && !desc.configurable()) { // 14b i. Append key as an element of targetNonconfigurableKeys. target_nonconfigurable_keys->set(nonconfigurable_keys_length, target_keys->get(i)); nonconfigurable_keys_length++; // The key was moved, null it out in the original list. target_keys->set(i, Smi::kZero); } else { // 14c. Else, // 14c i. Append key as an element of targetConfigurableKeys. // (No-op, just keep it in |target_keys|.) } } // 15. If extensibleTarget is true and targetNonconfigurableKeys is empty, // then: if (extensible_target && nonconfigurable_keys_length == 0) { // 15a. Return trapResult. return AddKeysFromJSProxy(proxy, trap_result); } // 16. Let uncheckedResultKeys be a new List which is a copy of trapResult. Zone set_zone(isolate_->allocator(), ZONE_NAME); ZoneAllocationPolicy alloc(&set_zone); const int kPresent = 1; const int kGone = 0; base::TemplateHashMapImpl, int, NameComparator, ZoneAllocationPolicy> unchecked_result_keys(ZoneHashMap::kDefaultHashMapCapacity, NameComparator(isolate_), alloc); int unchecked_result_keys_size = 0; for (int i = 0; i < trap_result->length(); ++i) { Handle key(Name::cast(trap_result->get(i)), isolate_); auto entry = unchecked_result_keys.LookupOrInsert(key, key->Hash(), alloc); if (entry->value != kPresent) { entry->value = kPresent; unchecked_result_keys_size++; } } // 17. Repeat, for each key that is an element of targetNonconfigurableKeys: for (int i = 0; i < nonconfigurable_keys_length; ++i) { Object* raw_key = target_nonconfigurable_keys->get(i); Handle key(Name::cast(raw_key), isolate_); // 17a. If key is not an element of uncheckedResultKeys, throw a // TypeError exception. auto found = unchecked_result_keys.Lookup(key, key->Hash()); if (found == nullptr || found->value == kGone) { isolate_->Throw(*isolate_->factory()->NewTypeError( MessageTemplate::kProxyOwnKeysMissing, key)); return Nothing(); } // 17b. Remove key from uncheckedResultKeys. found->value = kGone; unchecked_result_keys_size--; } // 18. If extensibleTarget is true, return trapResult. if (extensible_target) { return AddKeysFromJSProxy(proxy, trap_result); } // 19. Repeat, for each key that is an element of targetConfigurableKeys: for (int i = 0; i < target_configurable_keys->length(); ++i) { Object* raw_key = target_configurable_keys->get(i); if (raw_key->IsSmi()) continue; // Zapped entry, was nonconfigurable. Handle key(Name::cast(raw_key), isolate_); // 19a. If key is not an element of uncheckedResultKeys, throw a // TypeError exception. auto found = unchecked_result_keys.Lookup(key, key->Hash()); if (found == nullptr || found->value == kGone) { isolate_->Throw(*isolate_->factory()->NewTypeError( MessageTemplate::kProxyOwnKeysMissing, key)); return Nothing(); } // 19b. Remove key from uncheckedResultKeys. found->value = kGone; unchecked_result_keys_size--; } // 20. If uncheckedResultKeys is not empty, throw a TypeError exception. if (unchecked_result_keys_size != 0) { DCHECK_GT(unchecked_result_keys_size, 0); isolate_->Throw(*isolate_->factory()->NewTypeError( MessageTemplate::kProxyOwnKeysNonExtensible)); return Nothing(); } // 21. Return trapResult. return AddKeysFromJSProxy(proxy, trap_result); } Maybe KeyAccumulator::CollectOwnJSProxyTargetKeys( Handle proxy, Handle target) { // TODO(cbruni): avoid creating another KeyAccumulator Handle keys; ASSIGN_RETURN_ON_EXCEPTION_VALUE( isolate_, keys, KeyAccumulator::GetKeys( target, KeyCollectionMode::kOwnOnly, ALL_PROPERTIES, GetKeysConversion::kConvertToString, is_for_in_, skip_indices_), Nothing()); Maybe result = AddKeysFromJSProxy(proxy, keys); return result; } } // namespace internal } // namespace v8