Chromium Code Reviews| Index: src/keys.cc |
| diff --git a/src/keys.cc b/src/keys.cc |
| index f8b606ca4bf1cd9b8ddd07ad1d0c75d9532ddb7b..1be63fa0c3689680215a1ae2d962f0cda32b471e 100644 |
| --- a/src/keys.cc |
| +++ b/src/keys.cc |
| @@ -4,10 +4,13 @@ |
| #include "src/keys.h" |
| +#include "src/api-arguments.h" |
| #include "src/elements.h" |
| #include "src/factory.h" |
| +#include "src/identity-map.h" |
| #include "src/isolate-inl.h" |
| #include "src/objects-inl.h" |
| +#include "src/objects.h" |
|
Jakob Kummerow
2016/05/03 14:56:12
nit: no need to include objects.h if you already h
|
| #include "src/property-descriptor.h" |
| #include "src/prototype.h" |
| @@ -312,6 +315,48 @@ void KeyAccumulator::NextPrototype() { |
| level_symbol_length_ = 0; |
| } |
| +// Helper function for JSReceiver::GetKeys() below. Can be called recursively. |
|
Jakob Kummerow
2016/05/03 14:56:12
nit: outdated comment
|
| +// Returns |true| or |nothing|. |
| +Maybe<bool> KeyAccumulator::GetKeys_Internal(Handle<JSReceiver> receiver, |
| + Handle<JSReceiver> object, |
| + KeyCollectionType type) { |
| + // Proxies have no hidden prototype and we should not trigger the |
| + // [[GetPrototypeOf]] trap on the last iteration when using |
| + // AdvanceFollowingProxies. |
| + if (type == OWN_ONLY && object->IsJSProxy()) { |
| + MAYBE_RETURN( |
| + JSProxyOwnPropertyKeys(receiver, Handle<JSProxy>::cast(object)), |
| + Nothing<bool>()); |
| + return Just(true); |
| + } |
| + |
| + PrototypeIterator::WhereToEnd end = type == OWN_ONLY |
| + ? PrototypeIterator::END_AT_NON_HIDDEN |
| + : PrototypeIterator::END_AT_NULL; |
| + for (PrototypeIterator iter(isolate_, object, |
| + PrototypeIterator::START_AT_RECEIVER, end); |
| + !iter.IsAtEnd();) { |
| + Handle<JSReceiver> current = |
| + PrototypeIterator::GetCurrent<JSReceiver>(iter); |
| + Maybe<bool> result = Just(false); // Dummy initialization. |
| + if (current->IsJSProxy()) { |
| + result = JSProxyOwnPropertyKeys(receiver, Handle<JSProxy>::cast(current)); |
| + } else { |
| + DCHECK(current->IsJSObject()); |
| + result = |
| + GetKeysFromJSObject(receiver, Handle<JSObject>::cast(current), type); |
| + } |
| + MAYBE_RETURN(result, Nothing<bool>()); |
| + 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 GetKeysFromJSObject. |
| + if (!iter.AdvanceFollowingProxiesIgnoringAccessChecks()) { |
| + return Nothing<bool>(); |
| + } |
| + } |
| + return Just(true); |
| +} |
| + |
| namespace { |
| void TrySettingEmptyEnumCache(JSReceiver* object) { |
| @@ -363,6 +408,89 @@ void FastKeyAccumulator::Prepare() { |
| } |
| namespace { |
| +static Handle<FixedArray> ReduceFixedArrayTo(Isolate* isolate, |
| + Handle<FixedArray> array, |
| + int length) { |
| + DCHECK_LE(length, array->length()); |
| + if (array->length() == length) return array; |
| + return isolate->factory()->CopyFixedArrayUpTo(array, length); |
| +} |
| + |
| +Handle<FixedArray> GetFastEnumPropertyKeys(Isolate* isolate, |
| + Handle<JSObject> object) { |
| + Handle<Map> map(object->map()); |
| + bool cache_enum_length = map->OnlyHasSimpleProperties(); |
| + |
| + Handle<DescriptorArray> descs = |
| + Handle<DescriptorArray>(map->instance_descriptors(), isolate); |
| + int own_property_count = map->EnumLength(); |
| + // If the enum length of the given map is set to kInvalidEnumCache, this |
| + // means that the map itself has never used the present enum cache. The |
| + // first step to using the cache is to set the enum length of the map by |
| + // counting the number of own descriptors that are ENUMERABLE_STRINGS. |
| + if (own_property_count == kInvalidEnumCacheSentinel) { |
| + own_property_count = |
| + map->NumberOfDescribedProperties(OWN_DESCRIPTORS, ENUMERABLE_STRINGS); |
| + } else { |
| + DCHECK( |
| + own_property_count == |
| + map->NumberOfDescribedProperties(OWN_DESCRIPTORS, ENUMERABLE_STRINGS)); |
| + } |
| + |
| + if (descs->HasEnumCache()) { |
| + Handle<FixedArray> keys(descs->GetEnumCache(), isolate); |
| + // In case the number of properties required in the enum are actually |
| + // present, we can reuse the enum cache. Otherwise, this means that the |
| + // enum cache was generated for a previous (smaller) version of the |
| + // Descriptor Array. In that case we regenerate the enum cache. |
| + if (own_property_count <= keys->length()) { |
| + isolate->counters()->enum_cache_hits()->Increment(); |
| + if (cache_enum_length) map->SetEnumLength(own_property_count); |
| + return ReduceFixedArrayTo(isolate, keys, own_property_count); |
| + } |
| + } |
| + |
| + if (descs->IsEmpty()) { |
| + isolate->counters()->enum_cache_hits()->Increment(); |
| + if (cache_enum_length) map->SetEnumLength(0); |
| + return isolate->factory()->empty_fixed_array(); |
| + } |
| + |
| + isolate->counters()->enum_cache_misses()->Increment(); |
| + |
| + Handle<FixedArray> storage = |
| + isolate->factory()->NewFixedArray(own_property_count); |
| + Handle<FixedArray> indices = |
| + isolate->factory()->NewFixedArray(own_property_count); |
| + |
| + int size = map->NumberOfOwnDescriptors(); |
| + int index = 0; |
| + |
| + for (int i = 0; i < size; i++) { |
| + PropertyDetails details = descs->GetDetails(i); |
| + if (details.IsDontEnum()) continue; |
| + Object* key = descs->GetKey(i); |
| + if (key->IsSymbol()) continue; |
| + storage->set(index, key); |
| + if (!indices.is_null()) { |
| + if (details.type() != DATA) { |
| + indices = Handle<FixedArray>(); |
| + } else { |
| + FieldIndex field_index = FieldIndex::ForDescriptor(*map, i); |
| + int load_by_field_index = field_index.GetLoadByFieldIndex(); |
| + indices->set(index, Smi::FromInt(load_by_field_index)); |
| + } |
| + } |
| + index++; |
| + } |
| + DCHECK(index == storage->length()); |
| + |
| + DescriptorArray::SetEnumCache(descs, isolate, storage, indices); |
| + if (cache_enum_length) { |
| + map->SetEnumLength(own_property_count); |
| + } |
| + return storage; |
| +} |
| template <bool fast_properties> |
| Handle<FixedArray> GetOwnKeysWithElements(Isolate* isolate, |
| @@ -371,10 +499,10 @@ Handle<FixedArray> GetOwnKeysWithElements(Isolate* isolate, |
| Handle<FixedArray> keys; |
| ElementsAccessor* accessor = object->GetElementsAccessor(); |
| if (fast_properties) { |
| - keys = JSObject::GetFastEnumPropertyKeys(isolate, object); |
| + keys = GetFastEnumPropertyKeys(isolate, object); |
| } else { |
| // TODO(cbruni): preallocate big enough array to also hold elements. |
| - keys = JSObject::GetEnumPropertyKeys(object); |
| + keys = KeyAccumulator::GetEnumPropertyKeys(isolate, object); |
| } |
| Handle<FixedArray> result = |
| accessor->PrependElementIndices(object, keys, convert, ONLY_ENUMERABLE); |
| @@ -402,7 +530,7 @@ MaybeHandle<FixedArray> GetOwnKeysWithUninitializedEnumCache( |
| } |
| // We have no elements but possibly enumerable property keys, hence we can |
| // directly initialize the enum cache. |
| - return JSObject::GetFastEnumPropertyKeys(isolate, object); |
| + return GetFastEnumPropertyKeys(isolate, object); |
| } |
| bool OnlyHasSimpleProperties(Map* map) { |
| @@ -461,5 +589,297 @@ MaybeHandle<FixedArray> FastKeyAccumulator::GetKeysSlow( |
| filter_proxy_keys_); |
| } |
| +enum IndexedOrNamed { kIndexed, kNamed }; |
| + |
| +// Returns |true| on success, |nothing| on exception. |
| +template <class Callback, IndexedOrNamed type> |
| +static Maybe<bool> GetKeysFromInterceptor(Handle<JSReceiver> receiver, |
| + Handle<JSObject> object, |
| + KeyAccumulator* accumulator) { |
| + Isolate* isolate = accumulator->isolate(); |
| + if (type == kIndexed) { |
| + if (!object->HasIndexedInterceptor()) return Just(true); |
| + } else { |
| + if (!object->HasNamedInterceptor()) return Just(true); |
| + } |
| + Handle<InterceptorInfo> interceptor(type == kIndexed |
| + ? object->GetIndexedInterceptor() |
| + : object->GetNamedInterceptor(), |
| + isolate); |
| + if ((accumulator->filter() & ONLY_ALL_CAN_READ) && |
| + !interceptor->all_can_read()) { |
| + return Just(true); |
| + } |
| + PropertyCallbackArguments args(isolate, interceptor->data(), *receiver, |
| + *object, Object::DONT_THROW); |
| + Handle<JSObject> result; |
| + if (!interceptor->enumerator()->IsUndefined()) { |
| + Callback enum_fun = v8::ToCData<Callback>(interceptor->enumerator()); |
| + const char* log_tag = type == kIndexed ? "interceptor-indexed-enum" |
| + : "interceptor-named-enum"; |
| + LOG(isolate, ApiObjectAccess(log_tag, *object)); |
| + result = args.Call(enum_fun); |
| + } |
| + RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, Nothing<bool>()); |
| + if (result.is_null()) return Just(true); |
| + DCHECK(result->IsJSArray() || result->HasSloppyArgumentsElements()); |
| + // The accumulator takes care of string/symbol filtering. |
| + if (type == kIndexed) { |
| + accumulator->AddElementKeysFromInterceptor(result); |
| + } else { |
| + accumulator->AddKeys(result, DO_NOT_CONVERT); |
| + } |
| + return Just(true); |
| +} |
| + |
| +void KeyAccumulator::CollectOwnElementKeys(Handle<JSObject> object) { |
| + if (filter_ & SKIP_STRINGS) return; |
| + ElementsAccessor* accessor = object->GetElementsAccessor(); |
| + accessor->CollectElementIndices(object, this, kMaxUInt32, filter_, 0); |
| +} |
| + |
| +void KeyAccumulator::CollectOwnPropertyNames(Handle<JSObject> object) { |
| + if (object->HasFastProperties()) { |
| + int real_size = object->map()->NumberOfOwnDescriptors(); |
| + Handle<DescriptorArray> descs(object->map()->instance_descriptors(), |
| + isolate_); |
| + for (int i = 0; i < real_size; i++) { |
| + PropertyDetails details = descs->GetDetails(i); |
| + if ((details.attributes() & filter_) != 0) continue; |
| + if (filter_ & ONLY_ALL_CAN_READ) { |
| + if (details.kind() != kAccessor) continue; |
| + Object* accessors = descs->GetValue(i); |
| + if (!accessors->IsAccessorInfo()) continue; |
| + if (!AccessorInfo::cast(accessors)->all_can_read()) continue; |
| + } |
| + Name* key = descs->GetKey(i); |
| + if (key->FilterKey(filter_)) continue; |
| + this->AddKey(key, DO_NOT_CONVERT); |
| + } |
| + } else if (object->IsJSGlobalObject()) { |
| + GlobalDictionary::CollectKeysTo( |
| + handle(object->global_dictionary(), isolate_), this, filter_); |
| + } else { |
| + NameDictionary::CollectKeysTo( |
| + handle(object->property_dictionary(), isolate_), this, filter_); |
| + } |
| +} |
| + |
| +// Returns |true| on success, |false| if prototype walking should be stopped, |
| +// |nothing| if an exception was thrown. |
| +Maybe<bool> KeyAccumulator::GetKeysFromJSObject(Handle<JSReceiver> receiver, |
| + Handle<JSObject> object, |
| + KeyCollectionType type) { |
| + this->NextPrototype(); |
| + // Check access rights if required. |
| + if (object->IsAccessCheckNeeded() && |
| + !isolate_->MayAccess(handle(isolate_->context()), object)) { |
| + // The cross-origin spec says that [[Enumerate]] shall return an empty |
| + // iterator when it doesn't have access... |
| + if (type == INCLUDE_PROTOS) { |
| + return Just(false); |
| + } |
| + // ...whereas [[OwnPropertyKeys]] shall return whitelisted properties. |
| + DCHECK_EQ(OWN_ONLY, type); |
| + filter_ = static_cast<PropertyFilter>(filter_ | ONLY_ALL_CAN_READ); |
| + } |
| + |
| + this->CollectOwnElementKeys(object); |
| + |
| + // Add the element keys from the interceptor. |
| + Maybe<bool> success = |
| + GetKeysFromInterceptor<v8::IndexedPropertyEnumeratorCallback, kIndexed>( |
| + receiver, object, this); |
| + MAYBE_RETURN(success, Nothing<bool>()); |
| + |
| + if (filter_ == ENUMERABLE_STRINGS) { |
| + Handle<FixedArray> enum_keys = |
| + KeyAccumulator::GetEnumPropertyKeys(isolate_, object); |
| + this->AddKeys(enum_keys, DO_NOT_CONVERT); |
| + } else { |
| + this->CollectOwnPropertyNames(object); |
| + } |
| + |
| + // Add the property keys from the interceptor. |
| + success = GetKeysFromInterceptor<v8::GenericNamedPropertyEnumeratorCallback, |
| + kNamed>(receiver, object, this); |
| + MAYBE_RETURN(success, Nothing<bool>()); |
| + return Just(true); |
| +} |
| + |
| +// static |
| +Handle<FixedArray> KeyAccumulator::GetEnumPropertyKeys( |
| + Isolate* isolate, Handle<JSObject> object) { |
| + if (object->HasFastProperties()) { |
| + return GetFastEnumPropertyKeys(isolate, object); |
| + } else if (object->IsJSGlobalObject()) { |
| + Handle<GlobalDictionary> dictionary(object->global_dictionary(), isolate); |
| + int length = dictionary->NumberOfEnumElements(); |
| + if (length == 0) { |
| + return isolate->factory()->empty_fixed_array(); |
| + } |
| + Handle<FixedArray> storage = isolate->factory()->NewFixedArray(length); |
| + dictionary->CopyEnumKeysTo(*storage); |
| + return storage; |
| + } else { |
| + Handle<NameDictionary> dictionary(object->property_dictionary(), isolate); |
| + int length = dictionary->NumberOfEnumElements(); |
| + if (length == 0) { |
| + return isolate->factory()->empty_fixed_array(); |
| + } |
| + Handle<FixedArray> storage = isolate->factory()->NewFixedArray(length); |
| + dictionary->CopyEnumKeysTo(*storage); |
| + return storage; |
| + } |
| +} |
| + |
| +// ES6 9.5.12 |
| +// Returns |true| on success, |nothing| in case of exception. |
| +Maybe<bool> KeyAccumulator::JSProxyOwnPropertyKeys(Handle<JSReceiver> receiver, |
| + Handle<JSProxy> proxy) { |
| + STACK_CHECK(isolate_, Nothing<bool>()); |
| + // 1. Let handler be the value of the [[ProxyHandler]] internal slot of O. |
| + Handle<Object> 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<bool>(); |
| + } |
| + // 4. Let target be the value of the [[ProxyTarget]] internal slot of O. |
| + Handle<JSReceiver> target(proxy->target(), isolate_); |
| + // 5. Let trap be ? GetMethod(handler, "ownKeys"). |
| + Handle<Object> trap; |
| + ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| + isolate_, trap, Object::GetMethod(Handle<JSReceiver>::cast(handler), |
| + isolate_->factory()->ownKeys_string()), |
| + Nothing<bool>()); |
| + // 6. If trap is undefined, then |
| + if (trap->IsUndefined()) { |
| + // 6a. Return target.[[OwnPropertyKeys]](). |
| + return this->GetKeys_Internal(receiver, target, OWN_ONLY); |
| + } |
| + // 7. Let trapResultArray be Call(trap, handler, «target»). |
| + Handle<Object> trap_result_array; |
| + Handle<Object> args[] = {target}; |
| + ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| + isolate_, trap_result_array, |
| + Execution::Call(isolate_, trap, handler, arraysize(args), args), |
| + Nothing<bool>()); |
| + // 8. Let trapResult be ? CreateListFromArrayLike(trapResultArray, |
| + // «String, Symbol»). |
| + Handle<FixedArray> trap_result; |
| + ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| + isolate_, trap_result, |
| + Object::CreateListFromArrayLike(isolate_, trap_result_array, |
| + ElementTypes::kStringAndSymbol), |
| + Nothing<bool>()); |
| + // 9. Let extensibleTarget be ? IsExtensible(target). |
| + Maybe<bool> maybe_extensible = JSReceiver::IsExtensible(target); |
| + MAYBE_RETURN(maybe_extensible, Nothing<bool>()); |
| + bool extensible_target = maybe_extensible.FromJust(); |
| + // 10. Let targetKeys be ? target.[[OwnPropertyKeys]](). |
| + Handle<FixedArray> target_keys; |
| + ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate_, target_keys, |
| + JSReceiver::OwnPropertyKeys(target), |
| + Nothing<bool>()); |
| + // 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<FixedArray> target_configurable_keys = target_keys; |
| + // 13. Let targetNonconfigurableKeys be an empty List. |
| + Handle<FixedArray> 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<bool> found = JSReceiver::GetOwnPropertyDescriptor( |
| + isolate_, target, handle(target_keys->get(i), isolate_), &desc); |
| + MAYBE_RETURN(found, Nothing<bool>()); |
| + // 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::FromInt(0)); |
| + } else { |
| + // 14c. Else, |
| + // 14c i. Append key as an element of targetConfigurableKeys. |
| + // (No-op, just keep it in |target_keys|.) |
| + } |
| + } |
| + this->NextPrototype(); // Prepare for accumulating keys. |
| + // 15. If extensibleTarget is true and targetNonconfigurableKeys is empty, |
| + // then: |
| + if (extensible_target && nonconfigurable_keys_length == 0) { |
| + // 15a. Return trapResult. |
| + return this->AddKeysFromProxy(proxy, trap_result); |
| + } |
| + // 16. Let uncheckedResultKeys be a new List which is a copy of trapResult. |
| + Zone set_zone(isolate_->allocator()); |
| + const int kPresent = 1; |
| + const int kGone = 0; |
| + IdentityMap<int> unchecked_result_keys(isolate_->heap(), &set_zone); |
| + int unchecked_result_keys_size = 0; |
| + for (int i = 0; i < trap_result->length(); ++i) { |
| + DCHECK(trap_result->get(i)->IsUniqueName()); |
| + Object* key = trap_result->get(i); |
| + int* entry = unchecked_result_keys.Get(key); |
| + if (*entry != kPresent) { |
| + *entry = 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* key = target_nonconfigurable_keys->get(i); |
| + // 17a. If key is not an element of uncheckedResultKeys, throw a |
| + // TypeError exception. |
| + int* found = unchecked_result_keys.Find(key); |
| + if (found == nullptr || *found == kGone) { |
| + isolate_->Throw(*isolate_->factory()->NewTypeError( |
| + MessageTemplate::kProxyOwnKeysMissing, handle(key, isolate_))); |
| + return Nothing<bool>(); |
| + } |
| + // 17b. Remove key from uncheckedResultKeys. |
| + *found = kGone; |
| + unchecked_result_keys_size--; |
| + } |
| + // 18. If extensibleTarget is true, return trapResult. |
| + if (extensible_target) { |
| + return this->AddKeysFromProxy(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* key = target_configurable_keys->get(i); |
| + if (key->IsSmi()) continue; // Zapped entry, was nonconfigurable. |
| + // 19a. If key is not an element of uncheckedResultKeys, throw a |
| + // TypeError exception. |
| + int* found = unchecked_result_keys.Find(key); |
| + if (found == nullptr || *found == kGone) { |
| + isolate_->Throw(*isolate_->factory()->NewTypeError( |
| + MessageTemplate::kProxyOwnKeysMissing, handle(key, isolate_))); |
| + return Nothing<bool>(); |
| + } |
| + // 19b. Remove key from uncheckedResultKeys. |
| + *found = 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<bool>(); |
| + } |
| + // 21. Return trapResult. |
| + return this->AddKeysFromProxy(proxy, trap_result); |
| +} |
| + |
| } // namespace internal |
| } // namespace v8 |