Home | History | Annotate | Download | only in src
      1 // Copyright 2013 the V8 project authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "src/keys.h"
      6 
      7 #include "src/api-arguments-inl.h"
      8 #include "src/elements-inl.h"
      9 #include "src/handles-inl.h"
     10 #include "src/heap/factory.h"
     11 #include "src/identity-map.h"
     12 #include "src/isolate-inl.h"
     13 #include "src/objects-inl.h"
     14 #include "src/objects/api-callbacks.h"
     15 #include "src/objects/hash-table-inl.h"
     16 #include "src/objects/module-inl.h"
     17 #include "src/property-descriptor.h"
     18 #include "src/prototype.h"
     19 
     20 namespace v8 {
     21 namespace internal {
     22 
     23 KeyAccumulator::~KeyAccumulator() {
     24 }
     25 
     26 namespace {
     27 
     28 static bool ContainsOnlyValidKeys(Handle<FixedArray> array) {
     29   int len = array->length();
     30   for (int i = 0; i < len; i++) {
     31     Object* e = array->get(i);
     32     if (!(e->IsName() || e->IsNumber())) return false;
     33   }
     34   return true;
     35 }
     36 
     37 }  // namespace
     38 
     39 // static
     40 MaybeHandle<FixedArray> KeyAccumulator::GetKeys(
     41     Handle<JSReceiver> object, KeyCollectionMode mode, PropertyFilter filter,
     42     GetKeysConversion keys_conversion, bool is_for_in, bool skip_indices) {
     43   Isolate* isolate = object->GetIsolate();
     44   FastKeyAccumulator accumulator(isolate, object, mode, filter, is_for_in,
     45                                  skip_indices);
     46   return accumulator.GetKeys(keys_conversion);
     47 }
     48 
     49 Handle<FixedArray> KeyAccumulator::GetKeys(GetKeysConversion convert) {
     50   if (keys_.is_null()) {
     51     return isolate_->factory()->empty_fixed_array();
     52   }
     53   if (mode_ == KeyCollectionMode::kOwnOnly &&
     54       keys_->map() == ReadOnlyRoots(isolate_).fixed_array_map()) {
     55     return Handle<FixedArray>::cast(keys_);
     56   }
     57   USE(ContainsOnlyValidKeys);
     58   Handle<FixedArray> result =
     59       OrderedHashSet::ConvertToKeysArray(isolate(), keys(), convert);
     60   DCHECK(ContainsOnlyValidKeys(result));
     61   return result;
     62 }
     63 
     64 Handle<OrderedHashSet> KeyAccumulator::keys() {
     65   return Handle<OrderedHashSet>::cast(keys_);
     66 }
     67 
     68 void KeyAccumulator::AddKey(Object* key, AddKeyConversion convert) {
     69   AddKey(handle(key, isolate_), convert);
     70 }
     71 
     72 void KeyAccumulator::AddKey(Handle<Object> key, AddKeyConversion convert) {
     73   if (key->IsSymbol()) {
     74     if (filter_ & SKIP_SYMBOLS) return;
     75     if (Handle<Symbol>::cast(key)->is_private()) return;
     76   } else if (filter_ & SKIP_STRINGS) {
     77     return;
     78   }
     79   if (IsShadowed(key)) return;
     80   if (keys_.is_null()) {
     81     keys_ = OrderedHashSet::Allocate(isolate_, 16);
     82   }
     83   uint32_t index;
     84   if (convert == CONVERT_TO_ARRAY_INDEX && key->IsString() &&
     85       Handle<String>::cast(key)->AsArrayIndex(&index)) {
     86     key = isolate_->factory()->NewNumberFromUint(index);
     87   }
     88   Handle<OrderedHashSet> new_set = OrderedHashSet::Add(isolate(), keys(), key);
     89   if (*new_set != *keys_) {
     90     // The keys_ Set is converted directly to a FixedArray in GetKeys which can
     91     // be left-trimmer. Hence the previous Set should not keep a pointer to the
     92     // new one.
     93     keys_->set(OrderedHashTableBase::kNextTableIndex, Smi::kZero);
     94     keys_ = new_set;
     95   }
     96 }
     97 
     98 void KeyAccumulator::AddKeys(Handle<FixedArray> array,
     99                              AddKeyConversion convert) {
    100   int add_length = array->length();
    101   for (int i = 0; i < add_length; i++) {
    102     Handle<Object> current(array->get(i), isolate_);
    103     AddKey(current, convert);
    104   }
    105 }
    106 
    107 void KeyAccumulator::AddKeys(Handle<JSObject> array_like,
    108                              AddKeyConversion convert) {
    109   DCHECK(array_like->IsJSArray() || array_like->HasSloppyArgumentsElements());
    110   ElementsAccessor* accessor = array_like->GetElementsAccessor();
    111   accessor->AddElementsToKeyAccumulator(array_like, this, convert);
    112 }
    113 
    114 MaybeHandle<FixedArray> FilterProxyKeys(KeyAccumulator* accumulator,
    115                                         Handle<JSProxy> owner,
    116                                         Handle<FixedArray> keys,
    117                                         PropertyFilter filter) {
    118   if (filter == ALL_PROPERTIES) {
    119     // Nothing to do.
    120     return keys;
    121   }
    122   Isolate* isolate = accumulator->isolate();
    123   int store_position = 0;
    124   for (int i = 0; i < keys->length(); ++i) {
    125     Handle<Name> key(Name::cast(keys->get(i)), isolate);
    126     if (key->FilterKey(filter)) continue;  // Skip this key.
    127     if (filter & ONLY_ENUMERABLE) {
    128       PropertyDescriptor desc;
    129       Maybe<bool> found =
    130           JSProxy::GetOwnPropertyDescriptor(isolate, owner, key, &desc);
    131       MAYBE_RETURN(found, MaybeHandle<FixedArray>());
    132       if (!found.FromJust()) continue;
    133       if (!desc.enumerable()) {
    134         accumulator->AddShadowingKey(key);
    135         continue;
    136       }
    137     }
    138     // Keep this key.
    139     if (store_position != i) {
    140       keys->set(store_position, *key);
    141     }
    142     store_position++;
    143   }
    144   return FixedArray::ShrinkOrEmpty(isolate, keys, store_position);
    145 }
    146 
    147 // Returns "nothing" in case of exception, "true" on success.
    148 Maybe<bool> KeyAccumulator::AddKeysFromJSProxy(Handle<JSProxy> proxy,
    149                                                Handle<FixedArray> keys) {
    150   // Postpone the enumerable check for for-in to the ForInFilter step.
    151   if (!is_for_in_) {
    152     ASSIGN_RETURN_ON_EXCEPTION_VALUE(
    153         isolate_, keys, FilterProxyKeys(this, proxy, keys, filter_),
    154         Nothing<bool>());
    155     if (mode_ == KeyCollectionMode::kOwnOnly) {
    156       // If we collect only the keys from a JSProxy do not sort or deduplicate.
    157       keys_ = keys;
    158       return Just(true);
    159     }
    160   }
    161   AddKeys(keys, is_for_in_ ? CONVERT_TO_ARRAY_INDEX : DO_NOT_CONVERT);
    162   return Just(true);
    163 }
    164 
    165 Maybe<bool> KeyAccumulator::CollectKeys(Handle<JSReceiver> receiver,
    166                                         Handle<JSReceiver> object) {
    167   // Proxies have no hidden prototype and we should not trigger the
    168   // [[GetPrototypeOf]] trap on the last iteration when using
    169   // AdvanceFollowingProxies.
    170   if (mode_ == KeyCollectionMode::kOwnOnly && object->IsJSProxy()) {
    171     MAYBE_RETURN(CollectOwnJSProxyKeys(receiver, Handle<JSProxy>::cast(object)),
    172                  Nothing<bool>());
    173     return Just(true);
    174   }
    175 
    176   PrototypeIterator::WhereToEnd end = mode_ == KeyCollectionMode::kOwnOnly
    177                                           ? PrototypeIterator::END_AT_NON_HIDDEN
    178                                           : PrototypeIterator::END_AT_NULL;
    179   for (PrototypeIterator iter(isolate_, object, kStartAtReceiver, end);
    180        !iter.IsAtEnd();) {
    181     // Start the shadow checks only after the first prototype has added
    182     // shadowing keys.
    183     if (HasShadowingKeys()) skip_shadow_check_ = false;
    184     Handle<JSReceiver> current =
    185         PrototypeIterator::GetCurrent<JSReceiver>(iter);
    186     Maybe<bool> result = Just(false);  // Dummy initialization.
    187     if (current->IsJSProxy()) {
    188       result = CollectOwnJSProxyKeys(receiver, Handle<JSProxy>::cast(current));
    189     } else {
    190       DCHECK(current->IsJSObject());
    191       result = CollectOwnKeys(receiver, Handle<JSObject>::cast(current));
    192     }
    193     MAYBE_RETURN(result, Nothing<bool>());
    194     if (!result.FromJust()) break;  // |false| means "stop iterating".
    195     // Iterate through proxies but ignore access checks for the ALL_CAN_READ
    196     // case on API objects for OWN_ONLY keys handled in CollectOwnKeys.
    197     if (!iter.AdvanceFollowingProxiesIgnoringAccessChecks()) {
    198       return Nothing<bool>();
    199     }
    200     if (!last_non_empty_prototype_.is_null() &&
    201         *last_non_empty_prototype_ == *current) {
    202       break;
    203     }
    204   }
    205   return Just(true);
    206 }
    207 
    208 bool KeyAccumulator::HasShadowingKeys() { return !shadowing_keys_.is_null(); }
    209 
    210 bool KeyAccumulator::IsShadowed(Handle<Object> key) {
    211   if (!HasShadowingKeys() || skip_shadow_check_) return false;
    212   return shadowing_keys_->Has(isolate_, key);
    213 }
    214 
    215 void KeyAccumulator::AddShadowingKey(Object* key) {
    216   if (mode_ == KeyCollectionMode::kOwnOnly) return;
    217   AddShadowingKey(handle(key, isolate_));
    218 }
    219 void KeyAccumulator::AddShadowingKey(Handle<Object> key) {
    220   if (mode_ == KeyCollectionMode::kOwnOnly) return;
    221   if (shadowing_keys_.is_null()) {
    222     shadowing_keys_ = ObjectHashSet::New(isolate_, 16);
    223   }
    224   shadowing_keys_ = ObjectHashSet::Add(isolate(), shadowing_keys_, key);
    225 }
    226 
    227 namespace {
    228 
    229 void TrySettingEmptyEnumCache(JSReceiver* object) {
    230   Map* map = object->map();
    231   DCHECK_EQ(kInvalidEnumCacheSentinel, map->EnumLength());
    232   if (!map->OnlyHasSimpleProperties()) return;
    233   if (map->IsJSProxyMap()) return;
    234   if (map->NumberOfEnumerableProperties() > 0) return;
    235   DCHECK(object->IsJSObject());
    236   map->SetEnumLength(0);
    237 }
    238 
    239 bool CheckAndInitalizeEmptyEnumCache(JSReceiver* object) {
    240   if (object->map()->EnumLength() == kInvalidEnumCacheSentinel) {
    241     TrySettingEmptyEnumCache(object);
    242   }
    243   if (object->map()->EnumLength() != 0) return false;
    244   DCHECK(object->IsJSObject());
    245   return !JSObject::cast(object)->HasEnumerableElements();
    246 }
    247 }  // namespace
    248 
    249 void FastKeyAccumulator::Prepare() {
    250   DisallowHeapAllocation no_gc;
    251   // Directly go for the fast path for OWN_ONLY keys.
    252   if (mode_ == KeyCollectionMode::kOwnOnly) return;
    253   // Fully walk the prototype chain and find the last prototype with keys.
    254   is_receiver_simple_enum_ = false;
    255   has_empty_prototype_ = true;
    256   JSReceiver* last_prototype = nullptr;
    257   for (PrototypeIterator iter(isolate_, *receiver_); !iter.IsAtEnd();
    258        iter.Advance()) {
    259     JSReceiver* current = iter.GetCurrent<JSReceiver>();
    260     bool has_no_properties = CheckAndInitalizeEmptyEnumCache(current);
    261     if (has_no_properties) continue;
    262     last_prototype = current;
    263     has_empty_prototype_ = false;
    264   }
    265   if (has_empty_prototype_) {
    266     is_receiver_simple_enum_ =
    267         receiver_->map()->EnumLength() != kInvalidEnumCacheSentinel &&
    268         !JSObject::cast(*receiver_)->HasEnumerableElements();
    269   } else if (last_prototype != nullptr) {
    270     last_non_empty_prototype_ = handle(last_prototype, isolate_);
    271   }
    272 }
    273 
    274 namespace {
    275 
    276 Handle<FixedArray> ReduceFixedArrayTo(Isolate* isolate,
    277                                       Handle<FixedArray> array, int length) {
    278   DCHECK_LE(length, array->length());
    279   if (array->length() == length) return array;
    280   return isolate->factory()->CopyFixedArrayUpTo(array, length);
    281 }
    282 
    283 // Initializes and directly returns the enume cache. Users of this function
    284 // have to make sure to never directly leak the enum cache.
    285 Handle<FixedArray> GetFastEnumPropertyKeys(Isolate* isolate,
    286                                            Handle<JSObject> object) {
    287   Handle<Map> map(object->map(), isolate);
    288   Handle<FixedArray> keys(map->instance_descriptors()->GetEnumCache()->keys(),
    289                           isolate);
    290 
    291   // Check if the {map} has a valid enum length, which implies that it
    292   // must have a valid enum cache as well.
    293   int enum_length = map->EnumLength();
    294   if (enum_length != kInvalidEnumCacheSentinel) {
    295     DCHECK(map->OnlyHasSimpleProperties());
    296     DCHECK_LE(enum_length, keys->length());
    297     DCHECK_EQ(enum_length, map->NumberOfEnumerableProperties());
    298     isolate->counters()->enum_cache_hits()->Increment();
    299     return ReduceFixedArrayTo(isolate, keys, enum_length);
    300   }
    301 
    302   // Determine the actual number of enumerable properties of the {map}.
    303   enum_length = map->NumberOfEnumerableProperties();
    304 
    305   // Check if there's already a shared enum cache on the {map}s
    306   // DescriptorArray with sufficient number of entries.
    307   if (enum_length <= keys->length()) {
    308     if (map->OnlyHasSimpleProperties()) map->SetEnumLength(enum_length);
    309     isolate->counters()->enum_cache_hits()->Increment();
    310     return ReduceFixedArrayTo(isolate, keys, enum_length);
    311   }
    312 
    313   Handle<DescriptorArray> descriptors =
    314       Handle<DescriptorArray>(map->instance_descriptors(), isolate);
    315   isolate->counters()->enum_cache_misses()->Increment();
    316   int nod = map->NumberOfOwnDescriptors();
    317 
    318   // Create the keys array.
    319   int index = 0;
    320   bool fields_only = true;
    321   keys = isolate->factory()->NewFixedArray(enum_length);
    322   for (int i = 0; i < nod; i++) {
    323     DisallowHeapAllocation no_gc;
    324     PropertyDetails details = descriptors->GetDetails(i);
    325     if (details.IsDontEnum()) continue;
    326     Object* key = descriptors->GetKey(i);
    327     if (key->IsSymbol()) continue;
    328     keys->set(index, key);
    329     if (details.location() != kField) fields_only = false;
    330     index++;
    331   }
    332   DCHECK_EQ(index, keys->length());
    333 
    334   // Optionally also create the indices array.
    335   Handle<FixedArray> indices = isolate->factory()->empty_fixed_array();
    336   if (fields_only) {
    337     indices = isolate->factory()->NewFixedArray(enum_length);
    338     index = 0;
    339     for (int i = 0; i < nod; i++) {
    340       DisallowHeapAllocation no_gc;
    341       PropertyDetails details = descriptors->GetDetails(i);
    342       if (details.IsDontEnum()) continue;
    343       Object* key = descriptors->GetKey(i);
    344       if (key->IsSymbol()) continue;
    345       DCHECK_EQ(kData, details.kind());
    346       DCHECK_EQ(kField, details.location());
    347       FieldIndex field_index = FieldIndex::ForDescriptor(*map, i);
    348       indices->set(index, Smi::FromInt(field_index.GetLoadByFieldIndex()));
    349       index++;
    350     }
    351     DCHECK_EQ(index, indices->length());
    352   }
    353 
    354   DescriptorArray::SetEnumCache(descriptors, isolate, keys, indices);
    355   if (map->OnlyHasSimpleProperties()) map->SetEnumLength(enum_length);
    356 
    357   return keys;
    358 }
    359 
    360 template <bool fast_properties>
    361 MaybeHandle<FixedArray> GetOwnKeysWithElements(Isolate* isolate,
    362                                                Handle<JSObject> object,
    363                                                GetKeysConversion convert,
    364                                                bool skip_indices) {
    365   Handle<FixedArray> keys;
    366   ElementsAccessor* accessor = object->GetElementsAccessor();
    367   if (fast_properties) {
    368     keys = GetFastEnumPropertyKeys(isolate, object);
    369   } else {
    370     // TODO(cbruni): preallocate big enough array to also hold elements.
    371     keys = KeyAccumulator::GetOwnEnumPropertyKeys(isolate, object);
    372   }
    373 
    374   MaybeHandle<FixedArray> result;
    375   if (skip_indices) {
    376     result = keys;
    377   } else {
    378     result =
    379         accessor->PrependElementIndices(object, keys, convert, ONLY_ENUMERABLE);
    380   }
    381 
    382   if (FLAG_trace_for_in_enumerate) {
    383     PrintF("| strings=%d symbols=0 elements=%u || prototypes>=1 ||\n",
    384            keys->length(), result.ToHandleChecked()->length() - keys->length());
    385   }
    386   return result;
    387 }
    388 
    389 }  // namespace
    390 
    391 MaybeHandle<FixedArray> FastKeyAccumulator::GetKeys(
    392     GetKeysConversion keys_conversion) {
    393   if (filter_ == ENUMERABLE_STRINGS) {
    394     Handle<FixedArray> keys;
    395     if (GetKeysFast(keys_conversion).ToHandle(&keys)) {
    396       return keys;
    397     }
    398     if (isolate_->has_pending_exception()) return MaybeHandle<FixedArray>();
    399   }
    400 
    401   return GetKeysSlow(keys_conversion);
    402 }
    403 
    404 MaybeHandle<FixedArray> FastKeyAccumulator::GetKeysFast(
    405     GetKeysConversion keys_conversion) {
    406   bool own_only = has_empty_prototype_ || mode_ == KeyCollectionMode::kOwnOnly;
    407   Map* map = receiver_->map();
    408   if (!own_only || map->IsCustomElementsReceiverMap()) {
    409     return MaybeHandle<FixedArray>();
    410   }
    411 
    412   // From this point on we are certain to only collect own keys.
    413   DCHECK(receiver_->IsJSObject());
    414   Handle<JSObject> object = Handle<JSObject>::cast(receiver_);
    415 
    416   // Do not try to use the enum-cache for dict-mode objects.
    417   if (map->is_dictionary_map()) {
    418     return GetOwnKeysWithElements<false>(isolate_, object, keys_conversion,
    419                                          skip_indices_);
    420   }
    421   int enum_length = receiver_->map()->EnumLength();
    422   if (enum_length == kInvalidEnumCacheSentinel) {
    423     Handle<FixedArray> keys;
    424     // Try initializing the enum cache and return own properties.
    425     if (GetOwnKeysWithUninitializedEnumCache().ToHandle(&keys)) {
    426       if (FLAG_trace_for_in_enumerate) {
    427         PrintF("| strings=%d symbols=0 elements=0 || prototypes>=1 ||\n",
    428                keys->length());
    429       }
    430       is_receiver_simple_enum_ =
    431           object->map()->EnumLength() != kInvalidEnumCacheSentinel;
    432       return keys;
    433     }
    434   }
    435   // The properties-only case failed because there were probably elements on the
    436   // receiver.
    437   return GetOwnKeysWithElements<true>(isolate_, object, keys_conversion,
    438                                       skip_indices_);
    439 }
    440 
    441 MaybeHandle<FixedArray>
    442 FastKeyAccumulator::GetOwnKeysWithUninitializedEnumCache() {
    443   Handle<JSObject> object = Handle<JSObject>::cast(receiver_);
    444   // Uninitalized enum cache
    445   Map* map = object->map();
    446   if (object->elements()->length() != 0) {
    447     // Assume that there are elements.
    448     return MaybeHandle<FixedArray>();
    449   }
    450   int number_of_own_descriptors = map->NumberOfOwnDescriptors();
    451   if (number_of_own_descriptors == 0) {
    452     map->SetEnumLength(0);
    453     return isolate_->factory()->empty_fixed_array();
    454   }
    455   // We have no elements but possibly enumerable property keys, hence we can
    456   // directly initialize the enum cache.
    457   Handle<FixedArray> keys = GetFastEnumPropertyKeys(isolate_, object);
    458   if (is_for_in_) return keys;
    459   // Do not leak the enum cache as it might end up as an elements backing store.
    460   return isolate_->factory()->CopyFixedArray(keys);
    461 }
    462 
    463 MaybeHandle<FixedArray> FastKeyAccumulator::GetKeysSlow(
    464     GetKeysConversion keys_conversion) {
    465   KeyAccumulator accumulator(isolate_, mode_, filter_);
    466   accumulator.set_is_for_in(is_for_in_);
    467   accumulator.set_skip_indices(skip_indices_);
    468   accumulator.set_last_non_empty_prototype(last_non_empty_prototype_);
    469 
    470   MAYBE_RETURN(accumulator.CollectKeys(receiver_, receiver_),
    471                MaybeHandle<FixedArray>());
    472   return accumulator.GetKeys(keys_conversion);
    473 }
    474 
    475 namespace {
    476 
    477 enum IndexedOrNamed { kIndexed, kNamed };
    478 
    479 void FilterForEnumerableProperties(Handle<JSReceiver> receiver,
    480                                    Handle<JSObject> object,
    481                                    Handle<InterceptorInfo> interceptor,
    482                                    KeyAccumulator* accumulator,
    483                                    Handle<JSObject> result,
    484                                    IndexedOrNamed type) {
    485   DCHECK(result->IsJSArray() || result->HasSloppyArgumentsElements());
    486   ElementsAccessor* accessor = result->GetElementsAccessor();
    487 
    488   uint32_t length = accessor->GetCapacity(*result, result->elements());
    489   for (uint32_t i = 0; i < length; i++) {
    490     if (!accessor->HasEntry(*result, i)) continue;
    491 
    492     // args are invalid after args.Call(), create a new one in every iteration.
    493     PropertyCallbackArguments args(accumulator->isolate(), interceptor->data(),
    494                                    *receiver, *object, kDontThrow);
    495 
    496     Handle<Object> element = accessor->Get(result, i);
    497     Handle<Object> attributes;
    498     if (type == kIndexed) {
    499       uint32_t number;
    500       CHECK(element->ToUint32(&number));
    501       attributes = args.CallIndexedQuery(interceptor, number);
    502     } else {
    503       CHECK(element->IsName());
    504       attributes =
    505           args.CallNamedQuery(interceptor, Handle<Name>::cast(element));
    506     }
    507 
    508     if (!attributes.is_null()) {
    509       int32_t value;
    510       CHECK(attributes->ToInt32(&value));
    511       if ((value & DONT_ENUM) == 0) {
    512         accumulator->AddKey(element, DO_NOT_CONVERT);
    513       }
    514     }
    515   }
    516 }
    517 
    518 // Returns |true| on success, |nothing| on exception.
    519 Maybe<bool> CollectInterceptorKeysInternal(Handle<JSReceiver> receiver,
    520                                            Handle<JSObject> object,
    521                                            Handle<InterceptorInfo> interceptor,
    522                                            KeyAccumulator* accumulator,
    523                                            IndexedOrNamed type) {
    524   Isolate* isolate = accumulator->isolate();
    525   PropertyCallbackArguments enum_args(isolate, interceptor->data(), *receiver,
    526                                       *object, kDontThrow);
    527 
    528   Handle<JSObject> result;
    529   if (!interceptor->enumerator()->IsUndefined(isolate)) {
    530     if (type == kIndexed) {
    531       result = enum_args.CallIndexedEnumerator(interceptor);
    532     } else {
    533       DCHECK_EQ(type, kNamed);
    534       result = enum_args.CallNamedEnumerator(interceptor);
    535     }
    536   }
    537   RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, Nothing<bool>());
    538   if (result.is_null()) return Just(true);
    539 
    540   if ((accumulator->filter() & ONLY_ENUMERABLE) &&
    541       !interceptor->query()->IsUndefined(isolate)) {
    542     FilterForEnumerableProperties(receiver, object, interceptor, accumulator,
    543                                   result, type);
    544   } else {
    545     accumulator->AddKeys(
    546         result, type == kIndexed ? CONVERT_TO_ARRAY_INDEX : DO_NOT_CONVERT);
    547   }
    548   return Just(true);
    549 }
    550 
    551 Maybe<bool> CollectInterceptorKeys(Handle<JSReceiver> receiver,
    552                                    Handle<JSObject> object,
    553                                    KeyAccumulator* accumulator,
    554                                    IndexedOrNamed type) {
    555   Isolate* isolate = accumulator->isolate();
    556   if (type == kIndexed) {
    557     if (!object->HasIndexedInterceptor()) return Just(true);
    558   } else {
    559     if (!object->HasNamedInterceptor()) return Just(true);
    560   }
    561   Handle<InterceptorInfo> interceptor(type == kIndexed
    562                                           ? object->GetIndexedInterceptor()
    563                                           : object->GetNamedInterceptor(),
    564                                       isolate);
    565   if ((accumulator->filter() & ONLY_ALL_CAN_READ) &&
    566       !interceptor->all_can_read()) {
    567     return Just(true);
    568   }
    569   return CollectInterceptorKeysInternal(receiver, object, interceptor,
    570                                         accumulator, type);
    571 }
    572 
    573 }  // namespace
    574 
    575 Maybe<bool> KeyAccumulator::CollectOwnElementIndices(
    576     Handle<JSReceiver> receiver, Handle<JSObject> object) {
    577   if (filter_ & SKIP_STRINGS || skip_indices_) return Just(true);
    578 
    579   ElementsAccessor* accessor = object->GetElementsAccessor();
    580   accessor->CollectElementIndices(object, this);
    581 
    582   return CollectInterceptorKeys(receiver, object, this, kIndexed);
    583 }
    584 
    585 namespace {
    586 
    587 template <bool skip_symbols>
    588 int CollectOwnPropertyNamesInternal(Handle<JSObject> object,
    589                                     KeyAccumulator* keys,
    590                                     Handle<DescriptorArray> descs,
    591                                     int start_index, int limit) {
    592   int first_skipped = -1;
    593   PropertyFilter filter = keys->filter();
    594   KeyCollectionMode mode = keys->mode();
    595   for (int i = start_index; i < limit; i++) {
    596     bool is_shadowing_key = false;
    597     PropertyDetails details = descs->GetDetails(i);
    598 
    599     if ((details.attributes() & filter) != 0) {
    600       if (mode == KeyCollectionMode::kIncludePrototypes) {
    601         is_shadowing_key = true;
    602       } else {
    603         continue;
    604       }
    605     }
    606 
    607     if (filter & ONLY_ALL_CAN_READ) {
    608       if (details.kind() != kAccessor) continue;
    609       Object* accessors = descs->GetStrongValue(i);
    610       if (!accessors->IsAccessorInfo()) continue;
    611       if (!AccessorInfo::cast(accessors)->all_can_read()) continue;
    612     }
    613 
    614     Name* key = descs->GetKey(i);
    615     if (skip_symbols == key->IsSymbol()) {
    616       if (first_skipped == -1) first_skipped = i;
    617       continue;
    618     }
    619     if (key->FilterKey(keys->filter())) continue;
    620 
    621     if (is_shadowing_key) {
    622       keys->AddShadowingKey(key);
    623     } else {
    624       keys->AddKey(key, DO_NOT_CONVERT);
    625     }
    626   }
    627   return first_skipped;
    628 }
    629 
    630 template <class T>
    631 Handle<FixedArray> GetOwnEnumPropertyDictionaryKeys(Isolate* isolate,
    632                                                     KeyCollectionMode mode,
    633                                                     KeyAccumulator* accumulator,
    634                                                     Handle<JSObject> object,
    635                                                     T* raw_dictionary) {
    636   Handle<T> dictionary(raw_dictionary, isolate);
    637   int length = dictionary->NumberOfEnumerableProperties();
    638   if (length == 0) {
    639     return isolate->factory()->empty_fixed_array();
    640   }
    641   Handle<FixedArray> storage = isolate->factory()->NewFixedArray(length);
    642   T::CopyEnumKeysTo(isolate, dictionary, storage, mode, accumulator);
    643   return storage;
    644 }
    645 }  // namespace
    646 
    647 Maybe<bool> KeyAccumulator::CollectOwnPropertyNames(Handle<JSReceiver> receiver,
    648                                                     Handle<JSObject> object) {
    649   if (filter_ == ENUMERABLE_STRINGS) {
    650     Handle<FixedArray> enum_keys;
    651     if (object->HasFastProperties()) {
    652       enum_keys = KeyAccumulator::GetOwnEnumPropertyKeys(isolate_, object);
    653       // If the number of properties equals the length of enumerable properties
    654       // we do not have to filter out non-enumerable ones
    655       Map* map = object->map();
    656       int nof_descriptors = map->NumberOfOwnDescriptors();
    657       if (enum_keys->length() != nof_descriptors) {
    658         Handle<DescriptorArray> descs =
    659             Handle<DescriptorArray>(map->instance_descriptors(), isolate_);
    660         for (int i = 0; i < nof_descriptors; i++) {
    661           PropertyDetails details = descs->GetDetails(i);
    662           if (!details.IsDontEnum()) continue;
    663           Object* key = descs->GetKey(i);
    664           this->AddShadowingKey(key);
    665         }
    666       }
    667     } else if (object->IsJSGlobalObject()) {
    668       enum_keys = GetOwnEnumPropertyDictionaryKeys(
    669           isolate_, mode_, this, object,
    670           JSGlobalObject::cast(*object)->global_dictionary());
    671     } else {
    672       enum_keys = GetOwnEnumPropertyDictionaryKeys(
    673           isolate_, mode_, this, object, object->property_dictionary());
    674     }
    675     if (object->IsJSModuleNamespace()) {
    676       // Simulate [[GetOwnProperty]] for establishing enumerability, which
    677       // throws for uninitialized exports.
    678       for (int i = 0, n = enum_keys->length(); i < n; ++i) {
    679         Handle<String> key(String::cast(enum_keys->get(i)), isolate_);
    680         if (Handle<JSModuleNamespace>::cast(object)
    681                 ->GetExport(isolate(), key)
    682                 .is_null()) {
    683           return Nothing<bool>();
    684         }
    685       }
    686     }
    687     AddKeys(enum_keys, DO_NOT_CONVERT);
    688   } else {
    689     if (object->HasFastProperties()) {
    690       int limit = object->map()->NumberOfOwnDescriptors();
    691       Handle<DescriptorArray> descs(object->map()->instance_descriptors(),
    692                                     isolate_);
    693       // First collect the strings,
    694       int first_symbol =
    695           CollectOwnPropertyNamesInternal<true>(object, this, descs, 0, limit);
    696       // then the symbols.
    697       if (first_symbol != -1) {
    698         CollectOwnPropertyNamesInternal<false>(object, this, descs,
    699                                                first_symbol, limit);
    700       }
    701     } else if (object->IsJSGlobalObject()) {
    702       GlobalDictionary::CollectKeysTo(
    703           handle(JSGlobalObject::cast(*object)->global_dictionary(), isolate_),
    704           this);
    705     } else {
    706       NameDictionary::CollectKeysTo(
    707           handle(object->property_dictionary(), isolate_), this);
    708     }
    709   }
    710   // Add the property keys from the interceptor.
    711   return CollectInterceptorKeys(receiver, object, this, kNamed);
    712 }
    713 
    714 Maybe<bool> KeyAccumulator::CollectAccessCheckInterceptorKeys(
    715     Handle<AccessCheckInfo> access_check_info, Handle<JSReceiver> receiver,
    716     Handle<JSObject> object) {
    717   if (!skip_indices_) {
    718     MAYBE_RETURN((CollectInterceptorKeysInternal(
    719                      receiver, object,
    720                      handle(InterceptorInfo::cast(
    721                                 access_check_info->indexed_interceptor()),
    722                             isolate_),
    723                      this, kIndexed)),
    724                  Nothing<bool>());
    725   }
    726   MAYBE_RETURN(
    727       (CollectInterceptorKeysInternal(
    728           receiver, object,
    729           handle(InterceptorInfo::cast(access_check_info->named_interceptor()),
    730                  isolate_),
    731           this, kNamed)),
    732       Nothing<bool>());
    733   return Just(true);
    734 }
    735 
    736 // Returns |true| on success, |false| if prototype walking should be stopped,
    737 // |nothing| if an exception was thrown.
    738 Maybe<bool> KeyAccumulator::CollectOwnKeys(Handle<JSReceiver> receiver,
    739                                            Handle<JSObject> object) {
    740   // Check access rights if required.
    741   if (object->IsAccessCheckNeeded() &&
    742       !isolate_->MayAccess(handle(isolate_->context(), isolate_), object)) {
    743     // The cross-origin spec says that [[Enumerate]] shall return an empty
    744     // iterator when it doesn't have access...
    745     if (mode_ == KeyCollectionMode::kIncludePrototypes) {
    746       return Just(false);
    747     }
    748     // ...whereas [[OwnPropertyKeys]] shall return whitelisted properties.
    749     DCHECK_EQ(KeyCollectionMode::kOwnOnly, mode_);
    750     Handle<AccessCheckInfo> access_check_info;
    751     {
    752       DisallowHeapAllocation no_gc;
    753       AccessCheckInfo* maybe_info = AccessCheckInfo::Get(isolate_, object);
    754       if (maybe_info) access_check_info = handle(maybe_info, isolate_);
    755     }
    756     // We always have both kinds of interceptors or none.
    757     if (!access_check_info.is_null() &&
    758         access_check_info->named_interceptor()) {
    759       MAYBE_RETURN(CollectAccessCheckInterceptorKeys(access_check_info,
    760                                                      receiver, object),
    761                    Nothing<bool>());
    762       return Just(false);
    763     }
    764     filter_ = static_cast<PropertyFilter>(filter_ | ONLY_ALL_CAN_READ);
    765   }
    766   MAYBE_RETURN(CollectOwnElementIndices(receiver, object), Nothing<bool>());
    767   MAYBE_RETURN(CollectOwnPropertyNames(receiver, object), Nothing<bool>());
    768   return Just(true);
    769 }
    770 
    771 // static
    772 Handle<FixedArray> KeyAccumulator::GetOwnEnumPropertyKeys(
    773     Isolate* isolate, Handle<JSObject> object) {
    774   if (object->HasFastProperties()) {
    775     return GetFastEnumPropertyKeys(isolate, object);
    776   } else if (object->IsJSGlobalObject()) {
    777     return GetOwnEnumPropertyDictionaryKeys(
    778         isolate, KeyCollectionMode::kOwnOnly, nullptr, object,
    779         JSGlobalObject::cast(*object)->global_dictionary());
    780   } else {
    781     return GetOwnEnumPropertyDictionaryKeys(
    782         isolate, KeyCollectionMode::kOwnOnly, nullptr, object,
    783         object->property_dictionary());
    784   }
    785 }
    786 
    787 namespace {
    788 
    789 class NameComparator {
    790  public:
    791   explicit NameComparator(Isolate* isolate) : isolate_(isolate) {}
    792 
    793   bool operator()(uint32_t hash1, uint32_t hash2, const Handle<Name>& key1,
    794                   const Handle<Name>& key2) const {
    795     return Name::Equals(isolate_, key1, key2);
    796   }
    797 
    798  private:
    799   Isolate* isolate_;
    800 };
    801 
    802 }  // namespace
    803 
    804 // ES6 9.5.12
    805 // Returns |true| on success, |nothing| in case of exception.
    806 Maybe<bool> KeyAccumulator::CollectOwnJSProxyKeys(Handle<JSReceiver> receiver,
    807                                                   Handle<JSProxy> proxy) {
    808   STACK_CHECK(isolate_, Nothing<bool>());
    809   // 1. Let handler be the value of the [[ProxyHandler]] internal slot of O.
    810   Handle<Object> handler(proxy->handler(), isolate_);
    811   // 2. If handler is null, throw a TypeError exception.
    812   // 3. Assert: Type(handler) is Object.
    813   if (proxy->IsRevoked()) {
    814     isolate_->Throw(*isolate_->factory()->NewTypeError(
    815         MessageTemplate::kProxyRevoked, isolate_->factory()->ownKeys_string()));
    816     return Nothing<bool>();
    817   }
    818   // 4. Let target be the value of the [[ProxyTarget]] internal slot of O.
    819   Handle<JSReceiver> target(JSReceiver::cast(proxy->target()), isolate_);
    820   // 5. Let trap be ? GetMethod(handler, "ownKeys").
    821   Handle<Object> trap;
    822   ASSIGN_RETURN_ON_EXCEPTION_VALUE(
    823       isolate_, trap, Object::GetMethod(Handle<JSReceiver>::cast(handler),
    824                                         isolate_->factory()->ownKeys_string()),
    825       Nothing<bool>());
    826   // 6. If trap is undefined, then
    827   if (trap->IsUndefined(isolate_)) {
    828     // 6a. Return target.[[OwnPropertyKeys]]().
    829     return CollectOwnJSProxyTargetKeys(proxy, target);
    830   }
    831   // 7. Let trapResultArray be Call(trap, handler, target).
    832   Handle<Object> trap_result_array;
    833   Handle<Object> args[] = {target};
    834   ASSIGN_RETURN_ON_EXCEPTION_VALUE(
    835       isolate_, trap_result_array,
    836       Execution::Call(isolate_, trap, handler, arraysize(args), args),
    837       Nothing<bool>());
    838   // 8. Let trapResult be ? CreateListFromArrayLike(trapResultArray,
    839   //    String, Symbol).
    840   Handle<FixedArray> trap_result;
    841   ASSIGN_RETURN_ON_EXCEPTION_VALUE(
    842       isolate_, trap_result,
    843       Object::CreateListFromArrayLike(isolate_, trap_result_array,
    844                                       ElementTypes::kStringAndSymbol),
    845       Nothing<bool>());
    846   // 9. Let extensibleTarget be ? IsExtensible(target).
    847   Maybe<bool> maybe_extensible = JSReceiver::IsExtensible(target);
    848   MAYBE_RETURN(maybe_extensible, Nothing<bool>());
    849   bool extensible_target = maybe_extensible.FromJust();
    850   // 10. Let targetKeys be ? target.[[OwnPropertyKeys]]().
    851   Handle<FixedArray> target_keys;
    852   ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate_, target_keys,
    853                                    JSReceiver::OwnPropertyKeys(target),
    854                                    Nothing<bool>());
    855   // 11. (Assert)
    856   // 12. Let targetConfigurableKeys be an empty List.
    857   // To save memory, we're re-using target_keys and will modify it in-place.
    858   Handle<FixedArray> target_configurable_keys = target_keys;
    859   // 13. Let targetNonconfigurableKeys be an empty List.
    860   Handle<FixedArray> target_nonconfigurable_keys =
    861       isolate_->factory()->NewFixedArray(target_keys->length());
    862   int nonconfigurable_keys_length = 0;
    863   // 14. Repeat, for each element key of targetKeys:
    864   for (int i = 0; i < target_keys->length(); ++i) {
    865     // 14a. Let desc be ? target.[[GetOwnProperty]](key).
    866     PropertyDescriptor desc;
    867     Maybe<bool> found = JSReceiver::GetOwnPropertyDescriptor(
    868         isolate_, target, handle(target_keys->get(i), isolate_), &desc);
    869     MAYBE_RETURN(found, Nothing<bool>());
    870     // 14b. If desc is not undefined and desc.[[Configurable]] is false, then
    871     if (found.FromJust() && !desc.configurable()) {
    872       // 14b i. Append key as an element of targetNonconfigurableKeys.
    873       target_nonconfigurable_keys->set(nonconfigurable_keys_length,
    874                                        target_keys->get(i));
    875       nonconfigurable_keys_length++;
    876       // The key was moved, null it out in the original list.
    877       target_keys->set(i, Smi::kZero);
    878     } else {
    879       // 14c. Else,
    880       // 14c i. Append key as an element of targetConfigurableKeys.
    881       // (No-op, just keep it in |target_keys|.)
    882     }
    883   }
    884   // 15. If extensibleTarget is true and targetNonconfigurableKeys is empty,
    885   //     then:
    886   if (extensible_target && nonconfigurable_keys_length == 0) {
    887     // 15a. Return trapResult.
    888     return AddKeysFromJSProxy(proxy, trap_result);
    889   }
    890   // 16. Let uncheckedResultKeys be a new List which is a copy of trapResult.
    891   Zone set_zone(isolate_->allocator(), ZONE_NAME);
    892   ZoneAllocationPolicy alloc(&set_zone);
    893   const int kPresent = 1;
    894   const int kGone = 0;
    895   base::TemplateHashMapImpl<Handle<Name>, int, NameComparator,
    896                             ZoneAllocationPolicy>
    897       unchecked_result_keys(ZoneHashMap::kDefaultHashMapCapacity,
    898                             NameComparator(isolate_), alloc);
    899   int unchecked_result_keys_size = 0;
    900   for (int i = 0; i < trap_result->length(); ++i) {
    901     Handle<Name> key(Name::cast(trap_result->get(i)), isolate_);
    902     auto entry = unchecked_result_keys.LookupOrInsert(key, key->Hash(), alloc);
    903     if (entry->value != kPresent) {
    904       entry->value = kPresent;
    905       unchecked_result_keys_size++;
    906     }
    907   }
    908   // 17. Repeat, for each key that is an element of targetNonconfigurableKeys:
    909   for (int i = 0; i < nonconfigurable_keys_length; ++i) {
    910     Object* raw_key = target_nonconfigurable_keys->get(i);
    911     Handle<Name> key(Name::cast(raw_key), isolate_);
    912     // 17a. If key is not an element of uncheckedResultKeys, throw a
    913     //      TypeError exception.
    914     auto found = unchecked_result_keys.Lookup(key, key->Hash());
    915     if (found == nullptr || found->value == kGone) {
    916       isolate_->Throw(*isolate_->factory()->NewTypeError(
    917           MessageTemplate::kProxyOwnKeysMissing, key));
    918       return Nothing<bool>();
    919     }
    920     // 17b. Remove key from uncheckedResultKeys.
    921     found->value = kGone;
    922     unchecked_result_keys_size--;
    923   }
    924   // 18. If extensibleTarget is true, return trapResult.
    925   if (extensible_target) {
    926     return AddKeysFromJSProxy(proxy, trap_result);
    927   }
    928   // 19. Repeat, for each key that is an element of targetConfigurableKeys:
    929   for (int i = 0; i < target_configurable_keys->length(); ++i) {
    930     Object* raw_key = target_configurable_keys->get(i);
    931     if (raw_key->IsSmi()) continue;  // Zapped entry, was nonconfigurable.
    932     Handle<Name> key(Name::cast(raw_key), isolate_);
    933     // 19a. If key is not an element of uncheckedResultKeys, throw a
    934     //      TypeError exception.
    935     auto found = unchecked_result_keys.Lookup(key, key->Hash());
    936     if (found == nullptr || found->value == kGone) {
    937       isolate_->Throw(*isolate_->factory()->NewTypeError(
    938           MessageTemplate::kProxyOwnKeysMissing, key));
    939       return Nothing<bool>();
    940     }
    941     // 19b. Remove key from uncheckedResultKeys.
    942     found->value = kGone;
    943     unchecked_result_keys_size--;
    944   }
    945   // 20. If uncheckedResultKeys is not empty, throw a TypeError exception.
    946   if (unchecked_result_keys_size != 0) {
    947     DCHECK_GT(unchecked_result_keys_size, 0);
    948     isolate_->Throw(*isolate_->factory()->NewTypeError(
    949         MessageTemplate::kProxyOwnKeysNonExtensible));
    950     return Nothing<bool>();
    951   }
    952   // 21. Return trapResult.
    953   return AddKeysFromJSProxy(proxy, trap_result);
    954 }
    955 
    956 Maybe<bool> KeyAccumulator::CollectOwnJSProxyTargetKeys(
    957     Handle<JSProxy> proxy, Handle<JSReceiver> target) {
    958   // TODO(cbruni): avoid creating another KeyAccumulator
    959   Handle<FixedArray> keys;
    960   ASSIGN_RETURN_ON_EXCEPTION_VALUE(
    961       isolate_, keys,
    962       KeyAccumulator::GetKeys(
    963           target, KeyCollectionMode::kOwnOnly, ALL_PROPERTIES,
    964           GetKeysConversion::kConvertToString, is_for_in_, skip_indices_),
    965       Nothing<bool>());
    966   Maybe<bool> result = AddKeysFromJSProxy(proxy, keys);
    967   return result;
    968 }
    969 
    970 }  // namespace internal
    971 }  // namespace v8
    972