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.h"
      8 #include "src/elements.h"
      9 #include "src/factory.h"
     10 #include "src/identity-map.h"
     11 #include "src/isolate-inl.h"
     12 #include "src/objects-inl.h"
     13 #include "src/property-descriptor.h"
     14 #include "src/prototype.h"
     15 
     16 namespace v8 {
     17 namespace internal {
     18 
     19 KeyAccumulator::~KeyAccumulator() {
     20 }
     21 
     22 namespace {
     23 
     24 static bool ContainsOnlyValidKeys(Handle<FixedArray> array) {
     25   int len = array->length();
     26   for (int i = 0; i < len; i++) {
     27     Object* e = array->get(i);
     28     if (!(e->IsName() || e->IsNumber())) return false;
     29   }
     30   return true;
     31 }
     32 
     33 }  // namespace
     34 MaybeHandle<FixedArray> KeyAccumulator::GetKeys(
     35     Handle<JSReceiver> object, KeyCollectionMode mode, PropertyFilter filter,
     36     GetKeysConversion keys_conversion, bool filter_proxy_keys, bool is_for_in) {
     37   Isolate* isolate = object->GetIsolate();
     38   KeyAccumulator accumulator(isolate, mode, filter);
     39   accumulator.set_filter_proxy_keys(filter_proxy_keys);
     40   accumulator.set_is_for_in(is_for_in);
     41   MAYBE_RETURN(accumulator.CollectKeys(object, object),
     42                MaybeHandle<FixedArray>());
     43   return accumulator.GetKeys(keys_conversion);
     44 }
     45 
     46 Handle<FixedArray> KeyAccumulator::GetKeys(GetKeysConversion convert) {
     47   if (keys_.is_null()) {
     48     return isolate_->factory()->empty_fixed_array();
     49   }
     50   if (mode_ == KeyCollectionMode::kOwnOnly &&
     51       keys_->map() == isolate_->heap()->fixed_array_map()) {
     52     return Handle<FixedArray>::cast(keys_);
     53   }
     54   USE(ContainsOnlyValidKeys);
     55   Handle<FixedArray> result =
     56       OrderedHashSet::ConvertToKeysArray(keys(), convert);
     57   DCHECK(ContainsOnlyValidKeys(result));
     58   return result;
     59 }
     60 
     61 void KeyAccumulator::AddKey(Object* key, AddKeyConversion convert) {
     62   AddKey(handle(key, isolate_), convert);
     63 }
     64 
     65 void KeyAccumulator::AddKey(Handle<Object> key, AddKeyConversion convert) {
     66   if (key->IsSymbol()) {
     67     if (filter_ & SKIP_SYMBOLS) return;
     68     if (Handle<Symbol>::cast(key)->is_private()) return;
     69   } else if (filter_ & SKIP_STRINGS) {
     70     return;
     71   }
     72   if (keys_.is_null()) {
     73     keys_ = OrderedHashSet::Allocate(isolate_, 16);
     74   }
     75   uint32_t index;
     76   if (convert == CONVERT_TO_ARRAY_INDEX && key->IsString() &&
     77       Handle<String>::cast(key)->AsArrayIndex(&index)) {
     78     key = isolate_->factory()->NewNumberFromUint(index);
     79   }
     80   keys_ = OrderedHashSet::Add(keys(), key);
     81 }
     82 
     83 void KeyAccumulator::AddKeys(Handle<FixedArray> array,
     84                              AddKeyConversion convert) {
     85   int add_length = array->length();
     86   for (int i = 0; i < add_length; i++) {
     87     Handle<Object> current(array->get(i), isolate_);
     88     AddKey(current, convert);
     89   }
     90 }
     91 
     92 void KeyAccumulator::AddKeys(Handle<JSObject> array_like,
     93                              AddKeyConversion convert) {
     94   DCHECK(array_like->IsJSArray() || array_like->HasSloppyArgumentsElements());
     95   ElementsAccessor* accessor = array_like->GetElementsAccessor();
     96   accessor->AddElementsToKeyAccumulator(array_like, this, convert);
     97 }
     98 
     99 MaybeHandle<FixedArray> FilterProxyKeys(Isolate* isolate, Handle<JSProxy> owner,
    100                                         Handle<FixedArray> keys,
    101                                         PropertyFilter filter) {
    102   if (filter == ALL_PROPERTIES) {
    103     // Nothing to do.
    104     return keys;
    105   }
    106   int store_position = 0;
    107   for (int i = 0; i < keys->length(); ++i) {
    108     Handle<Name> key(Name::cast(keys->get(i)), isolate);
    109     if (key->FilterKey(filter)) continue;  // Skip this key.
    110     if (filter & ONLY_ENUMERABLE) {
    111       PropertyDescriptor desc;
    112       Maybe<bool> found =
    113           JSProxy::GetOwnPropertyDescriptor(isolate, owner, key, &desc);
    114       MAYBE_RETURN(found, MaybeHandle<FixedArray>());
    115       if (!found.FromJust() || !desc.enumerable()) continue;  // Skip this key.
    116     }
    117     // Keep this key.
    118     if (store_position != i) {
    119       keys->set(store_position, *key);
    120     }
    121     store_position++;
    122   }
    123   if (store_position == 0) return isolate->factory()->empty_fixed_array();
    124   keys->Shrink(store_position);
    125   return keys;
    126 }
    127 
    128 // Returns "nothing" in case of exception, "true" on success.
    129 Maybe<bool> KeyAccumulator::AddKeysFromJSProxy(Handle<JSProxy> proxy,
    130                                                Handle<FixedArray> keys) {
    131   if (filter_proxy_keys_) {
    132     DCHECK(!is_for_in_);
    133     ASSIGN_RETURN_ON_EXCEPTION_VALUE(
    134         isolate_, keys, FilterProxyKeys(isolate_, proxy, keys, filter_),
    135         Nothing<bool>());
    136   }
    137   if (mode_ == KeyCollectionMode::kOwnOnly && !is_for_in_) {
    138     // If we collect only the keys from a JSProxy do not sort or deduplicate it.
    139     keys_ = keys;
    140     return Just(true);
    141   }
    142   AddKeys(keys, is_for_in_ ? CONVERT_TO_ARRAY_INDEX : DO_NOT_CONVERT);
    143   return Just(true);
    144 }
    145 
    146 Maybe<bool> KeyAccumulator::CollectKeys(Handle<JSReceiver> receiver,
    147                                         Handle<JSReceiver> object) {
    148   // Proxies have no hidden prototype and we should not trigger the
    149   // [[GetPrototypeOf]] trap on the last iteration when using
    150   // AdvanceFollowingProxies.
    151   if (mode_ == KeyCollectionMode::kOwnOnly && object->IsJSProxy()) {
    152     MAYBE_RETURN(CollectOwnJSProxyKeys(receiver, Handle<JSProxy>::cast(object)),
    153                  Nothing<bool>());
    154     return Just(true);
    155   }
    156 
    157   PrototypeIterator::WhereToEnd end = mode_ == KeyCollectionMode::kOwnOnly
    158                                           ? PrototypeIterator::END_AT_NON_HIDDEN
    159                                           : PrototypeIterator::END_AT_NULL;
    160   for (PrototypeIterator iter(isolate_, object, kStartAtReceiver, end);
    161        !iter.IsAtEnd();) {
    162     Handle<JSReceiver> current =
    163         PrototypeIterator::GetCurrent<JSReceiver>(iter);
    164     Maybe<bool> result = Just(false);  // Dummy initialization.
    165     if (current->IsJSProxy()) {
    166       result = CollectOwnJSProxyKeys(receiver, Handle<JSProxy>::cast(current));
    167     } else {
    168       DCHECK(current->IsJSObject());
    169       result = CollectOwnKeys(receiver, Handle<JSObject>::cast(current));
    170     }
    171     MAYBE_RETURN(result, Nothing<bool>());
    172     if (!result.FromJust()) break;  // |false| means "stop iterating".
    173     // Iterate through proxies but ignore access checks for the ALL_CAN_READ
    174     // case on API objects for OWN_ONLY keys handled in CollectOwnKeys.
    175     if (!iter.AdvanceFollowingProxiesIgnoringAccessChecks()) {
    176       return Nothing<bool>();
    177     }
    178     if (!last_non_empty_prototype_.is_null() &&
    179         *last_non_empty_prototype_ == *current) {
    180       break;
    181     }
    182   }
    183   return Just(true);
    184 }
    185 
    186 namespace {
    187 
    188 void TrySettingEmptyEnumCache(JSReceiver* object) {
    189   Map* map = object->map();
    190   DCHECK_EQ(kInvalidEnumCacheSentinel, map->EnumLength());
    191   if (!map->OnlyHasSimpleProperties()) return;
    192   if (map->IsJSProxyMap()) return;
    193   if (map->NumberOfOwnDescriptors() > 0) {
    194     int number_of_enumerable_own_properties =
    195         map->NumberOfDescribedProperties(OWN_DESCRIPTORS, ENUMERABLE_STRINGS);
    196     if (number_of_enumerable_own_properties > 0) return;
    197   }
    198   DCHECK(object->IsJSObject());
    199   map->SetEnumLength(0);
    200 }
    201 
    202 bool CheckAndInitalizeSimpleEnumCache(JSReceiver* object) {
    203   if (object->map()->EnumLength() == kInvalidEnumCacheSentinel) {
    204     TrySettingEmptyEnumCache(object);
    205   }
    206   if (object->map()->EnumLength() != 0) return false;
    207   DCHECK(object->IsJSObject());
    208   return !JSObject::cast(object)->HasEnumerableElements();
    209 }
    210 }  // namespace
    211 
    212 void FastKeyAccumulator::Prepare() {
    213   DisallowHeapAllocation no_gc;
    214   // Directly go for the fast path for OWN_ONLY keys.
    215   if (mode_ == KeyCollectionMode::kOwnOnly) return;
    216   // Fully walk the prototype chain and find the last prototype with keys.
    217   is_receiver_simple_enum_ = false;
    218   has_empty_prototype_ = true;
    219   JSReceiver* last_prototype = nullptr;
    220   for (PrototypeIterator iter(isolate_, *receiver_); !iter.IsAtEnd();
    221        iter.Advance()) {
    222     JSReceiver* current = iter.GetCurrent<JSReceiver>();
    223     bool has_no_properties = CheckAndInitalizeSimpleEnumCache(current);
    224     if (has_no_properties) continue;
    225     last_prototype = current;
    226     has_empty_prototype_ = false;
    227   }
    228   if (has_empty_prototype_) {
    229     is_receiver_simple_enum_ =
    230         receiver_->map()->EnumLength() != kInvalidEnumCacheSentinel &&
    231         !JSObject::cast(*receiver_)->HasEnumerableElements();
    232   } else if (last_prototype != nullptr) {
    233     last_non_empty_prototype_ = handle(last_prototype, isolate_);
    234   }
    235 }
    236 
    237 namespace {
    238 static Handle<FixedArray> ReduceFixedArrayTo(Isolate* isolate,
    239                                              Handle<FixedArray> array,
    240                                              int length) {
    241   DCHECK_LE(length, array->length());
    242   if (array->length() == length) return array;
    243   return isolate->factory()->CopyFixedArrayUpTo(array, length);
    244 }
    245 
    246 Handle<FixedArray> GetFastEnumPropertyKeys(Isolate* isolate,
    247                                            Handle<JSObject> object) {
    248   Handle<Map> map(object->map());
    249   bool cache_enum_length = map->OnlyHasSimpleProperties();
    250 
    251   Handle<DescriptorArray> descs =
    252       Handle<DescriptorArray>(map->instance_descriptors(), isolate);
    253   int own_property_count = map->EnumLength();
    254   // If the enum length of the given map is set to kInvalidEnumCache, this
    255   // means that the map itself has never used the present enum cache. The
    256   // first step to using the cache is to set the enum length of the map by
    257   // counting the number of own descriptors that are ENUMERABLE_STRINGS.
    258   if (own_property_count == kInvalidEnumCacheSentinel) {
    259     own_property_count =
    260         map->NumberOfDescribedProperties(OWN_DESCRIPTORS, ENUMERABLE_STRINGS);
    261   } else {
    262     DCHECK(
    263         own_property_count ==
    264         map->NumberOfDescribedProperties(OWN_DESCRIPTORS, ENUMERABLE_STRINGS));
    265   }
    266 
    267   if (descs->HasEnumCache()) {
    268     Handle<FixedArray> keys(descs->GetEnumCache(), isolate);
    269     // In case the number of properties required in the enum are actually
    270     // present, we can reuse the enum cache. Otherwise, this means that the
    271     // enum cache was generated for a previous (smaller) version of the
    272     // Descriptor Array. In that case we regenerate the enum cache.
    273     if (own_property_count <= keys->length()) {
    274       isolate->counters()->enum_cache_hits()->Increment();
    275       if (cache_enum_length) map->SetEnumLength(own_property_count);
    276       return ReduceFixedArrayTo(isolate, keys, own_property_count);
    277     }
    278   }
    279 
    280   if (descs->IsEmpty()) {
    281     isolate->counters()->enum_cache_hits()->Increment();
    282     if (cache_enum_length) map->SetEnumLength(0);
    283     return isolate->factory()->empty_fixed_array();
    284   }
    285 
    286   isolate->counters()->enum_cache_misses()->Increment();
    287 
    288   Handle<FixedArray> storage =
    289       isolate->factory()->NewFixedArray(own_property_count);
    290   Handle<FixedArray> indices =
    291       isolate->factory()->NewFixedArray(own_property_count);
    292 
    293   int size = map->NumberOfOwnDescriptors();
    294   int index = 0;
    295 
    296   for (int i = 0; i < size; i++) {
    297     PropertyDetails details = descs->GetDetails(i);
    298     if (details.IsDontEnum()) continue;
    299     Object* key = descs->GetKey(i);
    300     if (key->IsSymbol()) continue;
    301     storage->set(index, key);
    302     if (!indices.is_null()) {
    303       if (details.type() != DATA) {
    304         indices = Handle<FixedArray>();
    305       } else {
    306         FieldIndex field_index = FieldIndex::ForDescriptor(*map, i);
    307         int load_by_field_index = field_index.GetLoadByFieldIndex();
    308         indices->set(index, Smi::FromInt(load_by_field_index));
    309       }
    310     }
    311     index++;
    312   }
    313   DCHECK(index == storage->length());
    314 
    315   DescriptorArray::SetEnumCache(descs, isolate, storage, indices);
    316   if (cache_enum_length) {
    317     map->SetEnumLength(own_property_count);
    318   }
    319   return storage;
    320 }
    321 
    322 template <bool fast_properties>
    323 Handle<FixedArray> GetOwnKeysWithElements(Isolate* isolate,
    324                                           Handle<JSObject> object,
    325                                           GetKeysConversion convert) {
    326   Handle<FixedArray> keys;
    327   ElementsAccessor* accessor = object->GetElementsAccessor();
    328   if (fast_properties) {
    329     keys = GetFastEnumPropertyKeys(isolate, object);
    330   } else {
    331     // TODO(cbruni): preallocate big enough array to also hold elements.
    332     keys = KeyAccumulator::GetEnumPropertyKeys(isolate, object);
    333   }
    334   Handle<FixedArray> result =
    335       accessor->PrependElementIndices(object, keys, convert, ONLY_ENUMERABLE);
    336 
    337   if (FLAG_trace_for_in_enumerate) {
    338     PrintF("| strings=%d symbols=0 elements=%u || prototypes>=1 ||\n",
    339            keys->length(), result->length() - keys->length());
    340   }
    341   return result;
    342 }
    343 
    344 MaybeHandle<FixedArray> GetOwnKeysWithUninitializedEnumCache(
    345     Isolate* isolate, Handle<JSObject> object) {
    346   // Uninitalized enum cache
    347   Map* map = object->map();
    348   if (object->elements() != isolate->heap()->empty_fixed_array() ||
    349       object->elements() != isolate->heap()->empty_slow_element_dictionary()) {
    350     // Assume that there are elements.
    351     return MaybeHandle<FixedArray>();
    352   }
    353   int number_of_own_descriptors = map->NumberOfOwnDescriptors();
    354   if (number_of_own_descriptors == 0) {
    355     map->SetEnumLength(0);
    356     return isolate->factory()->empty_fixed_array();
    357   }
    358   // We have no elements but possibly enumerable property keys, hence we can
    359   // directly initialize the enum cache.
    360   return GetFastEnumPropertyKeys(isolate, object);
    361 }
    362 
    363 bool OnlyHasSimpleProperties(Map* map) {
    364   return map->instance_type() > LAST_CUSTOM_ELEMENTS_RECEIVER;
    365 }
    366 
    367 }  // namespace
    368 
    369 MaybeHandle<FixedArray> FastKeyAccumulator::GetKeys(
    370     GetKeysConversion keys_conversion) {
    371   Handle<FixedArray> keys;
    372   if (filter_ == ENUMERABLE_STRINGS &&
    373       GetKeysFast(keys_conversion).ToHandle(&keys)) {
    374     return keys;
    375   }
    376   return GetKeysSlow(keys_conversion);
    377 }
    378 
    379 MaybeHandle<FixedArray> FastKeyAccumulator::GetKeysFast(
    380     GetKeysConversion keys_conversion) {
    381   bool own_only = has_empty_prototype_ || mode_ == KeyCollectionMode::kOwnOnly;
    382   Map* map = receiver_->map();
    383   if (!own_only || !OnlyHasSimpleProperties(map)) {
    384     return MaybeHandle<FixedArray>();
    385   }
    386 
    387   // From this point on we are certiain to only collect own keys.
    388   DCHECK(receiver_->IsJSObject());
    389   Handle<JSObject> object = Handle<JSObject>::cast(receiver_);
    390 
    391   // Do not try to use the enum-cache for dict-mode objects.
    392   if (map->is_dictionary_map()) {
    393     return GetOwnKeysWithElements<false>(isolate_, object, keys_conversion);
    394   }
    395   int enum_length = receiver_->map()->EnumLength();
    396   if (enum_length == kInvalidEnumCacheSentinel) {
    397     Handle<FixedArray> keys;
    398     // Try initializing the enum cache and return own properties.
    399     if (GetOwnKeysWithUninitializedEnumCache(isolate_, object)
    400             .ToHandle(&keys)) {
    401       if (FLAG_trace_for_in_enumerate) {
    402         PrintF("| strings=%d symbols=0 elements=0 || prototypes>=1 ||\n",
    403                keys->length());
    404       }
    405       is_receiver_simple_enum_ =
    406           object->map()->EnumLength() != kInvalidEnumCacheSentinel;
    407       return keys;
    408     }
    409   }
    410   // The properties-only case failed because there were probably elements on the
    411   // receiver.
    412   return GetOwnKeysWithElements<true>(isolate_, object, keys_conversion);
    413 }
    414 
    415 MaybeHandle<FixedArray> FastKeyAccumulator::GetKeysSlow(
    416     GetKeysConversion keys_conversion) {
    417   KeyAccumulator accumulator(isolate_, mode_, filter_);
    418   accumulator.set_filter_proxy_keys(filter_proxy_keys_);
    419   accumulator.set_is_for_in(is_for_in_);
    420   accumulator.set_last_non_empty_prototype(last_non_empty_prototype_);
    421 
    422   MAYBE_RETURN(accumulator.CollectKeys(receiver_, receiver_),
    423                MaybeHandle<FixedArray>());
    424   return accumulator.GetKeys(keys_conversion);
    425 }
    426 
    427 namespace {
    428 
    429 enum IndexedOrNamed { kIndexed, kNamed };
    430 
    431 // Returns |true| on success, |nothing| on exception.
    432 template <class Callback, IndexedOrNamed type>
    433 Maybe<bool> CollectInterceptorKeysInternal(Handle<JSReceiver> receiver,
    434                                            Handle<JSObject> object,
    435                                            Handle<InterceptorInfo> interceptor,
    436                                            KeyAccumulator* accumulator) {
    437   Isolate* isolate = accumulator->isolate();
    438   PropertyCallbackArguments args(isolate, interceptor->data(), *receiver,
    439                                  *object, Object::DONT_THROW);
    440   Handle<JSObject> result;
    441   if (!interceptor->enumerator()->IsUndefined(isolate)) {
    442     Callback enum_fun = v8::ToCData<Callback>(interceptor->enumerator());
    443     const char* log_tag = type == kIndexed ? "interceptor-indexed-enum"
    444                                            : "interceptor-named-enum";
    445     LOG(isolate, ApiObjectAccess(log_tag, *object));
    446     result = args.Call(enum_fun);
    447   }
    448   RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, Nothing<bool>());
    449   if (result.is_null()) return Just(true);
    450   accumulator->AddKeys(
    451       result, type == kIndexed ? CONVERT_TO_ARRAY_INDEX : DO_NOT_CONVERT);
    452   return Just(true);
    453 }
    454 
    455 template <class Callback, IndexedOrNamed type>
    456 Maybe<bool> CollectInterceptorKeys(Handle<JSReceiver> receiver,
    457                                    Handle<JSObject> object,
    458                                    KeyAccumulator* accumulator) {
    459   Isolate* isolate = accumulator->isolate();
    460   if (type == kIndexed) {
    461     if (!object->HasIndexedInterceptor()) return Just(true);
    462   } else {
    463     if (!object->HasNamedInterceptor()) return Just(true);
    464   }
    465   Handle<InterceptorInfo> interceptor(type == kIndexed
    466                                           ? object->GetIndexedInterceptor()
    467                                           : object->GetNamedInterceptor(),
    468                                       isolate);
    469   if ((accumulator->filter() & ONLY_ALL_CAN_READ) &&
    470       !interceptor->all_can_read()) {
    471     return Just(true);
    472   }
    473   return CollectInterceptorKeysInternal<Callback, type>(
    474       receiver, object, interceptor, accumulator);
    475 }
    476 
    477 }  // namespace
    478 
    479 Maybe<bool> KeyAccumulator::CollectOwnElementIndices(
    480     Handle<JSReceiver> receiver, Handle<JSObject> object) {
    481   if (filter_ & SKIP_STRINGS || skip_indices_) return Just(true);
    482 
    483   ElementsAccessor* accessor = object->GetElementsAccessor();
    484   accessor->CollectElementIndices(object, this);
    485 
    486   return CollectInterceptorKeys<v8::IndexedPropertyEnumeratorCallback,
    487                                 kIndexed>(receiver, object, this);
    488 }
    489 
    490 namespace {
    491 
    492 template <bool skip_symbols>
    493 int CollectOwnPropertyNamesInternal(Handle<JSObject> object,
    494                                     KeyAccumulator* keys,
    495                                     Handle<DescriptorArray> descs,
    496                                     int start_index, int limit) {
    497   int first_skipped = -1;
    498   for (int i = start_index; i < limit; i++) {
    499     PropertyDetails details = descs->GetDetails(i);
    500     if ((details.attributes() & keys->filter()) != 0) continue;
    501     if (keys->filter() & ONLY_ALL_CAN_READ) {
    502       if (details.kind() != kAccessor) continue;
    503       Object* accessors = descs->GetValue(i);
    504       if (!accessors->IsAccessorInfo()) continue;
    505       if (!AccessorInfo::cast(accessors)->all_can_read()) continue;
    506     }
    507     Name* key = descs->GetKey(i);
    508     if (skip_symbols == key->IsSymbol()) {
    509       if (first_skipped == -1) first_skipped = i;
    510       continue;
    511     }
    512     if (key->FilterKey(keys->filter())) continue;
    513     keys->AddKey(key, DO_NOT_CONVERT);
    514   }
    515   return first_skipped;
    516 }
    517 
    518 }  // namespace
    519 
    520 Maybe<bool> KeyAccumulator::CollectOwnPropertyNames(Handle<JSReceiver> receiver,
    521                                                     Handle<JSObject> object) {
    522   if (filter_ == ENUMERABLE_STRINGS) {
    523     Handle<FixedArray> enum_keys =
    524         KeyAccumulator::GetEnumPropertyKeys(isolate_, object);
    525     AddKeys(enum_keys, DO_NOT_CONVERT);
    526   } else {
    527     if (object->HasFastProperties()) {
    528       int limit = object->map()->NumberOfOwnDescriptors();
    529       Handle<DescriptorArray> descs(object->map()->instance_descriptors(),
    530                                     isolate_);
    531       // First collect the strings,
    532       int first_symbol =
    533           CollectOwnPropertyNamesInternal<true>(object, this, descs, 0, limit);
    534       // then the symbols.
    535       if (first_symbol != -1) {
    536         CollectOwnPropertyNamesInternal<false>(object, this, descs,
    537                                                first_symbol, limit);
    538       }
    539     } else if (object->IsJSGlobalObject()) {
    540       GlobalDictionary::CollectKeysTo(
    541           handle(object->global_dictionary(), isolate_), this, filter_);
    542     } else {
    543       NameDictionary::CollectKeysTo(
    544           handle(object->property_dictionary(), isolate_), this, filter_);
    545     }
    546   }
    547   // Add the property keys from the interceptor.
    548   return CollectInterceptorKeys<v8::GenericNamedPropertyEnumeratorCallback,
    549                                 kNamed>(receiver, object, this);
    550 }
    551 
    552 Maybe<bool> KeyAccumulator::CollectAccessCheckInterceptorKeys(
    553     Handle<AccessCheckInfo> access_check_info, Handle<JSReceiver> receiver,
    554     Handle<JSObject> object) {
    555   MAYBE_RETURN(
    556       (CollectInterceptorKeysInternal<v8::IndexedPropertyEnumeratorCallback,
    557                                       kIndexed>(
    558           receiver, object,
    559           handle(
    560               InterceptorInfo::cast(access_check_info->indexed_interceptor()),
    561               isolate_),
    562           this)),
    563       Nothing<bool>());
    564   MAYBE_RETURN(
    565       (CollectInterceptorKeysInternal<
    566           v8::GenericNamedPropertyEnumeratorCallback, kNamed>(
    567           receiver, object,
    568           handle(InterceptorInfo::cast(access_check_info->named_interceptor()),
    569                  isolate_),
    570           this)),
    571       Nothing<bool>());
    572   return Just(true);
    573 }
    574 
    575 // Returns |true| on success, |false| if prototype walking should be stopped,
    576 // |nothing| if an exception was thrown.
    577 Maybe<bool> KeyAccumulator::CollectOwnKeys(Handle<JSReceiver> receiver,
    578                                            Handle<JSObject> object) {
    579   // Check access rights if required.
    580   if (object->IsAccessCheckNeeded() &&
    581       !isolate_->MayAccess(handle(isolate_->context()), object)) {
    582     // The cross-origin spec says that [[Enumerate]] shall return an empty
    583     // iterator when it doesn't have access...
    584     if (mode_ == KeyCollectionMode::kIncludePrototypes) {
    585       return Just(false);
    586     }
    587     // ...whereas [[OwnPropertyKeys]] shall return whitelisted properties.
    588     DCHECK(KeyCollectionMode::kOwnOnly == mode_);
    589     Handle<AccessCheckInfo> access_check_info;
    590     {
    591       DisallowHeapAllocation no_gc;
    592       AccessCheckInfo* maybe_info = AccessCheckInfo::Get(isolate_, object);
    593       if (maybe_info) access_check_info = handle(maybe_info, isolate_);
    594     }
    595     // We always have both kinds of interceptors or none.
    596     if (!access_check_info.is_null() &&
    597         access_check_info->named_interceptor()) {
    598       MAYBE_RETURN(CollectAccessCheckInterceptorKeys(access_check_info,
    599                                                      receiver, object),
    600                    Nothing<bool>());
    601       return Just(false);
    602     }
    603     filter_ = static_cast<PropertyFilter>(filter_ | ONLY_ALL_CAN_READ);
    604   }
    605   MAYBE_RETURN(CollectOwnElementIndices(receiver, object), Nothing<bool>());
    606   MAYBE_RETURN(CollectOwnPropertyNames(receiver, object), Nothing<bool>());
    607   return Just(true);
    608 }
    609 
    610 // static
    611 Handle<FixedArray> KeyAccumulator::GetEnumPropertyKeys(
    612     Isolate* isolate, Handle<JSObject> object) {
    613   if (object->HasFastProperties()) {
    614     return GetFastEnumPropertyKeys(isolate, object);
    615   } else if (object->IsJSGlobalObject()) {
    616     Handle<GlobalDictionary> dictionary(object->global_dictionary(), isolate);
    617     int length = dictionary->NumberOfEnumElements();
    618     if (length == 0) {
    619       return isolate->factory()->empty_fixed_array();
    620     }
    621     Handle<FixedArray> storage = isolate->factory()->NewFixedArray(length);
    622     dictionary->CopyEnumKeysTo(*storage);
    623     return storage;
    624   } else {
    625     Handle<NameDictionary> dictionary(object->property_dictionary(), isolate);
    626     int length = dictionary->NumberOfEnumElements();
    627     if (length == 0) {
    628       return isolate->factory()->empty_fixed_array();
    629     }
    630     Handle<FixedArray> storage = isolate->factory()->NewFixedArray(length);
    631     dictionary->CopyEnumKeysTo(*storage);
    632     return storage;
    633   }
    634 }
    635 
    636 // ES6 9.5.12
    637 // Returns |true| on success, |nothing| in case of exception.
    638 Maybe<bool> KeyAccumulator::CollectOwnJSProxyKeys(Handle<JSReceiver> receiver,
    639                                                   Handle<JSProxy> proxy) {
    640   STACK_CHECK(isolate_, Nothing<bool>());
    641   // 1. Let handler be the value of the [[ProxyHandler]] internal slot of O.
    642   Handle<Object> handler(proxy->handler(), isolate_);
    643   // 2. If handler is null, throw a TypeError exception.
    644   // 3. Assert: Type(handler) is Object.
    645   if (proxy->IsRevoked()) {
    646     isolate_->Throw(*isolate_->factory()->NewTypeError(
    647         MessageTemplate::kProxyRevoked, isolate_->factory()->ownKeys_string()));
    648     return Nothing<bool>();
    649   }
    650   // 4. Let target be the value of the [[ProxyTarget]] internal slot of O.
    651   Handle<JSReceiver> target(proxy->target(), isolate_);
    652   // 5. Let trap be ? GetMethod(handler, "ownKeys").
    653   Handle<Object> trap;
    654   ASSIGN_RETURN_ON_EXCEPTION_VALUE(
    655       isolate_, trap, Object::GetMethod(Handle<JSReceiver>::cast(handler),
    656                                         isolate_->factory()->ownKeys_string()),
    657       Nothing<bool>());
    658   // 6. If trap is undefined, then
    659   if (trap->IsUndefined(isolate_)) {
    660     // 6a. Return target.[[OwnPropertyKeys]]().
    661     return CollectOwnJSProxyTargetKeys(proxy, target);
    662   }
    663   // 7. Let trapResultArray be Call(trap, handler, target).
    664   Handle<Object> trap_result_array;
    665   Handle<Object> args[] = {target};
    666   ASSIGN_RETURN_ON_EXCEPTION_VALUE(
    667       isolate_, trap_result_array,
    668       Execution::Call(isolate_, trap, handler, arraysize(args), args),
    669       Nothing<bool>());
    670   // 8. Let trapResult be ? CreateListFromArrayLike(trapResultArray,
    671   //    String, Symbol).
    672   Handle<FixedArray> trap_result;
    673   ASSIGN_RETURN_ON_EXCEPTION_VALUE(
    674       isolate_, trap_result,
    675       Object::CreateListFromArrayLike(isolate_, trap_result_array,
    676                                       ElementTypes::kStringAndSymbol),
    677       Nothing<bool>());
    678   // 9. Let extensibleTarget be ? IsExtensible(target).
    679   Maybe<bool> maybe_extensible = JSReceiver::IsExtensible(target);
    680   MAYBE_RETURN(maybe_extensible, Nothing<bool>());
    681   bool extensible_target = maybe_extensible.FromJust();
    682   // 10. Let targetKeys be ? target.[[OwnPropertyKeys]]().
    683   Handle<FixedArray> target_keys;
    684   ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate_, target_keys,
    685                                    JSReceiver::OwnPropertyKeys(target),
    686                                    Nothing<bool>());
    687   // 11. (Assert)
    688   // 12. Let targetConfigurableKeys be an empty List.
    689   // To save memory, we're re-using target_keys and will modify it in-place.
    690   Handle<FixedArray> target_configurable_keys = target_keys;
    691   // 13. Let targetNonconfigurableKeys be an empty List.
    692   Handle<FixedArray> target_nonconfigurable_keys =
    693       isolate_->factory()->NewFixedArray(target_keys->length());
    694   int nonconfigurable_keys_length = 0;
    695   // 14. Repeat, for each element key of targetKeys:
    696   for (int i = 0; i < target_keys->length(); ++i) {
    697     // 14a. Let desc be ? target.[[GetOwnProperty]](key).
    698     PropertyDescriptor desc;
    699     Maybe<bool> found = JSReceiver::GetOwnPropertyDescriptor(
    700         isolate_, target, handle(target_keys->get(i), isolate_), &desc);
    701     MAYBE_RETURN(found, Nothing<bool>());
    702     // 14b. If desc is not undefined and desc.[[Configurable]] is false, then
    703     if (found.FromJust() && !desc.configurable()) {
    704       // 14b i. Append key as an element of targetNonconfigurableKeys.
    705       target_nonconfigurable_keys->set(nonconfigurable_keys_length,
    706                                        target_keys->get(i));
    707       nonconfigurable_keys_length++;
    708       // The key was moved, null it out in the original list.
    709       target_keys->set(i, Smi::FromInt(0));
    710     } else {
    711       // 14c. Else,
    712       // 14c i. Append key as an element of targetConfigurableKeys.
    713       // (No-op, just keep it in |target_keys|.)
    714     }
    715   }
    716   // 15. If extensibleTarget is true and targetNonconfigurableKeys is empty,
    717   //     then:
    718   if (extensible_target && nonconfigurable_keys_length == 0) {
    719     // 15a. Return trapResult.
    720     return AddKeysFromJSProxy(proxy, trap_result);
    721   }
    722   // 16. Let uncheckedResultKeys be a new List which is a copy of trapResult.
    723   Zone set_zone(isolate_->allocator());
    724   const int kPresent = 1;
    725   const int kGone = 0;
    726   IdentityMap<int> unchecked_result_keys(isolate_->heap(), &set_zone);
    727   int unchecked_result_keys_size = 0;
    728   for (int i = 0; i < trap_result->length(); ++i) {
    729     DCHECK(trap_result->get(i)->IsUniqueName());
    730     Object* key = trap_result->get(i);
    731     int* entry = unchecked_result_keys.Get(key);
    732     if (*entry != kPresent) {
    733       *entry = kPresent;
    734       unchecked_result_keys_size++;
    735     }
    736   }
    737   // 17. Repeat, for each key that is an element of targetNonconfigurableKeys:
    738   for (int i = 0; i < nonconfigurable_keys_length; ++i) {
    739     Object* key = target_nonconfigurable_keys->get(i);
    740     // 17a. If key is not an element of uncheckedResultKeys, throw a
    741     //      TypeError exception.
    742     int* found = unchecked_result_keys.Find(key);
    743     if (found == nullptr || *found == kGone) {
    744       isolate_->Throw(*isolate_->factory()->NewTypeError(
    745           MessageTemplate::kProxyOwnKeysMissing, handle(key, isolate_)));
    746       return Nothing<bool>();
    747     }
    748     // 17b. Remove key from uncheckedResultKeys.
    749     *found = kGone;
    750     unchecked_result_keys_size--;
    751   }
    752   // 18. If extensibleTarget is true, return trapResult.
    753   if (extensible_target) {
    754     return AddKeysFromJSProxy(proxy, trap_result);
    755   }
    756   // 19. Repeat, for each key that is an element of targetConfigurableKeys:
    757   for (int i = 0; i < target_configurable_keys->length(); ++i) {
    758     Object* key = target_configurable_keys->get(i);
    759     if (key->IsSmi()) continue;  // Zapped entry, was nonconfigurable.
    760     // 19a. If key is not an element of uncheckedResultKeys, throw a
    761     //      TypeError exception.
    762     int* found = unchecked_result_keys.Find(key);
    763     if (found == nullptr || *found == kGone) {
    764       isolate_->Throw(*isolate_->factory()->NewTypeError(
    765           MessageTemplate::kProxyOwnKeysMissing, handle(key, isolate_)));
    766       return Nothing<bool>();
    767     }
    768     // 19b. Remove key from uncheckedResultKeys.
    769     *found = kGone;
    770     unchecked_result_keys_size--;
    771   }
    772   // 20. If uncheckedResultKeys is not empty, throw a TypeError exception.
    773   if (unchecked_result_keys_size != 0) {
    774     DCHECK_GT(unchecked_result_keys_size, 0);
    775     isolate_->Throw(*isolate_->factory()->NewTypeError(
    776         MessageTemplate::kProxyOwnKeysNonExtensible));
    777     return Nothing<bool>();
    778   }
    779   // 21. Return trapResult.
    780   return AddKeysFromJSProxy(proxy, trap_result);
    781 }
    782 
    783 Maybe<bool> KeyAccumulator::CollectOwnJSProxyTargetKeys(
    784     Handle<JSProxy> proxy, Handle<JSReceiver> target) {
    785   // TODO(cbruni): avoid creating another KeyAccumulator
    786   Handle<FixedArray> keys;
    787   ASSIGN_RETURN_ON_EXCEPTION_VALUE(
    788       isolate_, keys,
    789       KeyAccumulator::GetKeys(target, KeyCollectionMode::kOwnOnly, filter_,
    790                               GetKeysConversion::kConvertToString,
    791                               filter_proxy_keys_, is_for_in_),
    792       Nothing<bool>());
    793   bool prev_filter_proxy_keys_ = filter_proxy_keys_;
    794   filter_proxy_keys_ = false;
    795   Maybe<bool> result = AddKeysFromJSProxy(proxy, keys);
    796   filter_proxy_keys_ = prev_filter_proxy_keys_;
    797   return result;
    798 }
    799 
    800 }  // namespace internal
    801 }  // namespace v8
    802