| 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
|
|
|