Index: src/keys.cc |
diff --git a/src/keys.cc b/src/keys.cc |
index f8b606ca4bf1cd9b8ddd07ad1d0c75d9532ddb7b..2de68e6cdd862a7f4bba5dc0881095dfac73dfc4 100644 |
--- a/src/keys.cc |
+++ b/src/keys.cc |
@@ -4,8 +4,10 @@ |
#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/property-descriptor.h" |
@@ -312,6 +314,46 @@ void KeyAccumulator::NextPrototype() { |
level_symbol_length_ = 0; |
} |
+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 +405,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 +496,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 +527,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 +586,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 |