xref: /third_party/node/deps/v8/src/objects/keys.cc (revision 1cb0ef41)
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/objects/keys.h"
6
7#include "src/api/api-arguments-inl.h"
8#include "src/common/globals.h"
9#include "src/execution/isolate-inl.h"
10#include "src/handles/handles-inl.h"
11#include "src/heap/factory.h"
12#include "src/objects/api-callbacks.h"
13#include "src/objects/elements-inl.h"
14#include "src/objects/field-index-inl.h"
15#include "src/objects/hash-table-inl.h"
16#include "src/objects/module-inl.h"
17#include "src/objects/objects-inl.h"
18#include "src/objects/ordered-hash-table-inl.h"
19#include "src/objects/property-descriptor.h"
20#include "src/objects/prototype.h"
21#include "src/objects/slots-atomic-inl.h"
22#include "src/utils/identity-map.h"
23#include "src/zone/zone-hashmap.h"
24
25namespace v8 {
26namespace internal {
27
28#define RETURN_NOTHING_IF_NOT_SUCCESSFUL(call) \
29  do {                                         \
30    if (!(call)) return Nothing<bool>();       \
31  } while (false)
32
33#define RETURN_FAILURE_IF_NOT_SUCCESSFUL(call)          \
34  do {                                                  \
35    ExceptionStatus status_enum_result = (call);        \
36    if (!status_enum_result) return status_enum_result; \
37  } while (false)
38
39namespace {
40
41static bool ContainsOnlyValidKeys(Handle<FixedArray> array) {
42  int len = array->length();
43  for (int i = 0; i < len; i++) {
44    Object e = array->get(i);
45    if (!(e.IsName() || e.IsNumber())) return false;
46  }
47  return true;
48}
49
50static int AddKey(Object key, Handle<FixedArray> combined_keys,
51                  Handle<DescriptorArray> descs, int nof_descriptors,
52                  int target) {
53  for (InternalIndex i : InternalIndex::Range(nof_descriptors)) {
54    if (descs->GetKey(i) == key) return 0;
55  }
56  combined_keys->set(target, key);
57  return 1;
58}
59
60static Handle<FixedArray> CombineKeys(Isolate* isolate,
61                                      Handle<FixedArray> own_keys,
62                                      Handle<FixedArray> prototype_chain_keys,
63                                      Handle<JSReceiver> receiver,
64                                      bool may_have_elements) {
65  int prototype_chain_keys_length = prototype_chain_keys->length();
66  if (prototype_chain_keys_length == 0) return own_keys;
67
68  Map map = receiver->map();
69  int nof_descriptors = map.NumberOfOwnDescriptors();
70  if (nof_descriptors == 0 && !may_have_elements) return prototype_chain_keys;
71
72  Handle<DescriptorArray> descs(map.instance_descriptors(isolate), isolate);
73  int own_keys_length = own_keys.is_null() ? 0 : own_keys->length();
74  Handle<FixedArray> combined_keys = isolate->factory()->NewFixedArray(
75      own_keys_length + prototype_chain_keys_length);
76  if (own_keys_length != 0) {
77    own_keys->CopyTo(0, *combined_keys, 0, own_keys_length);
78  }
79  int target_keys_length = own_keys_length;
80  for (int i = 0; i < prototype_chain_keys_length; i++) {
81    target_keys_length += AddKey(prototype_chain_keys->get(i), combined_keys,
82                                 descs, nof_descriptors, target_keys_length);
83  }
84  return FixedArray::ShrinkOrEmpty(isolate, combined_keys, target_keys_length);
85}
86
87}  // namespace
88
89// static
90MaybeHandle<FixedArray> KeyAccumulator::GetKeys(
91    Handle<JSReceiver> object, KeyCollectionMode mode, PropertyFilter filter,
92    GetKeysConversion keys_conversion, bool is_for_in, bool skip_indices) {
93  Isolate* isolate = object->GetIsolate();
94  FastKeyAccumulator accumulator(isolate, object, mode, filter, is_for_in,
95                                 skip_indices);
96  return accumulator.GetKeys(keys_conversion);
97}
98
99Handle<FixedArray> KeyAccumulator::GetKeys(GetKeysConversion convert) {
100  if (keys_.is_null()) {
101    return isolate_->factory()->empty_fixed_array();
102  }
103  USE(ContainsOnlyValidKeys);
104  Handle<FixedArray> result =
105      OrderedHashSet::ConvertToKeysArray(isolate(), keys(), convert);
106  DCHECK(ContainsOnlyValidKeys(result));
107
108  if (try_prototype_info_cache_ && !first_prototype_map_.is_null()) {
109    PrototypeInfo::cast(first_prototype_map_->prototype_info())
110        .set_prototype_chain_enum_cache(*result);
111    Map::GetOrCreatePrototypeChainValidityCell(
112        Handle<Map>(receiver_->map(), isolate_), isolate_);
113    DCHECK(first_prototype_map_->IsPrototypeValidityCellValid());
114  }
115  return result;
116}
117
118Handle<OrderedHashSet> KeyAccumulator::keys() {
119  return Handle<OrderedHashSet>::cast(keys_);
120}
121
122ExceptionStatus KeyAccumulator::AddKey(Object key, AddKeyConversion convert) {
123  return AddKey(handle(key, isolate_), convert);
124}
125
126ExceptionStatus KeyAccumulator::AddKey(Handle<Object> key,
127                                       AddKeyConversion convert) {
128  if (filter_ == PRIVATE_NAMES_ONLY) {
129    if (!key->IsSymbol()) return ExceptionStatus::kSuccess;
130    if (!Symbol::cast(*key).is_private_name()) return ExceptionStatus::kSuccess;
131  } else if (key->IsSymbol()) {
132    if (filter_ & SKIP_SYMBOLS) return ExceptionStatus::kSuccess;
133    if (Symbol::cast(*key).is_private()) return ExceptionStatus::kSuccess;
134  } else if (filter_ & SKIP_STRINGS) {
135    return ExceptionStatus::kSuccess;
136  }
137
138  if (IsShadowed(key)) return ExceptionStatus::kSuccess;
139  if (keys_.is_null()) {
140    keys_ = OrderedHashSet::Allocate(isolate_, 16).ToHandleChecked();
141  }
142  uint32_t index;
143  if (convert == CONVERT_TO_ARRAY_INDEX && key->IsString() &&
144      Handle<String>::cast(key)->AsArrayIndex(&index)) {
145    key = isolate_->factory()->NewNumberFromUint(index);
146  }
147  MaybeHandle<OrderedHashSet> new_set_candidate =
148      OrderedHashSet::Add(isolate(), keys(), key);
149  Handle<OrderedHashSet> new_set;
150  if (!new_set_candidate.ToHandle(&new_set)) {
151    THROW_NEW_ERROR_RETURN_VALUE(
152        isolate_, NewRangeError(MessageTemplate::kTooManyProperties),
153        ExceptionStatus::kException);
154  }
155  if (*new_set != *keys_) {
156    // The keys_ Set is converted directly to a FixedArray in GetKeys which can
157    // be left-trimmer. Hence the previous Set should not keep a pointer to the
158    // new one.
159    keys_->set(OrderedHashSet::NextTableIndex(), Smi::zero());
160    keys_ = new_set;
161  }
162  return ExceptionStatus::kSuccess;
163}
164
165ExceptionStatus KeyAccumulator::AddKeys(Handle<FixedArray> array,
166                                        AddKeyConversion convert) {
167  int add_length = array->length();
168  for (int i = 0; i < add_length; i++) {
169    Handle<Object> current(array->get(i), isolate_);
170    RETURN_FAILURE_IF_NOT_SUCCESSFUL(AddKey(current, convert));
171  }
172  return ExceptionStatus::kSuccess;
173}
174
175ExceptionStatus KeyAccumulator::AddKeys(Handle<JSObject> array_like,
176                                        AddKeyConversion convert) {
177  DCHECK(array_like->IsJSArray() || array_like->HasSloppyArgumentsElements());
178  ElementsAccessor* accessor = array_like->GetElementsAccessor();
179  return accessor->AddElementsToKeyAccumulator(array_like, this, convert);
180}
181
182MaybeHandle<FixedArray> FilterProxyKeys(KeyAccumulator* accumulator,
183                                        Handle<JSProxy> owner,
184                                        Handle<FixedArray> keys,
185                                        PropertyFilter filter,
186                                        bool skip_indices) {
187  if (filter == ALL_PROPERTIES) {
188    // Nothing to do.
189    return keys;
190  }
191  Isolate* isolate = accumulator->isolate();
192  int store_position = 0;
193  for (int i = 0; i < keys->length(); ++i) {
194    Handle<Name> key(Name::cast(keys->get(i)), isolate);
195    if (key->FilterKey(filter)) continue;  // Skip this key.
196    if (skip_indices) {
197      uint32_t index;
198      if (key->AsArrayIndex(&index)) continue;  // Skip this key.
199    }
200    if (filter & ONLY_ENUMERABLE) {
201      PropertyDescriptor desc;
202      Maybe<bool> found =
203          JSProxy::GetOwnPropertyDescriptor(isolate, owner, key, &desc);
204      MAYBE_RETURN(found, MaybeHandle<FixedArray>());
205      if (!found.FromJust()) continue;
206      if (!desc.enumerable()) {
207        accumulator->AddShadowingKey(key);
208        continue;
209      }
210    }
211    // Keep this key.
212    if (store_position != i) {
213      keys->set(store_position, *key);
214    }
215    store_position++;
216  }
217  return FixedArray::ShrinkOrEmpty(isolate, keys, store_position);
218}
219
220// Returns "nothing" in case of exception, "true" on success.
221Maybe<bool> KeyAccumulator::AddKeysFromJSProxy(Handle<JSProxy> proxy,
222                                               Handle<FixedArray> keys) {
223  // Postpone the enumerable check for for-in to the ForInFilter step.
224  if (!is_for_in_) {
225    ASSIGN_RETURN_ON_EXCEPTION_VALUE(
226        isolate_, keys,
227        FilterProxyKeys(this, proxy, keys, filter_, skip_indices_),
228        Nothing<bool>());
229  }
230  // https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeys
231  // As of 10.5.11.9 says, the keys collected from Proxy should not contain
232  // any duplicates. And the order of the keys is preserved by the
233  // OrderedHashTable.
234  RETURN_NOTHING_IF_NOT_SUCCESSFUL(AddKeys(keys, CONVERT_TO_ARRAY_INDEX));
235  return Just(true);
236}
237
238Maybe<bool> KeyAccumulator::CollectKeys(Handle<JSReceiver> receiver,
239                                        Handle<JSReceiver> object) {
240  // Proxies have no hidden prototype and we should not trigger the
241  // [[GetPrototypeOf]] trap on the last iteration when using
242  // AdvanceFollowingProxies.
243  if (mode_ == KeyCollectionMode::kOwnOnly && object->IsJSProxy()) {
244    MAYBE_RETURN(CollectOwnJSProxyKeys(receiver, Handle<JSProxy>::cast(object)),
245                 Nothing<bool>());
246    return Just(true);
247  }
248
249  PrototypeIterator::WhereToEnd end = mode_ == KeyCollectionMode::kOwnOnly
250                                          ? PrototypeIterator::END_AT_NON_HIDDEN
251                                          : PrototypeIterator::END_AT_NULL;
252  for (PrototypeIterator iter(isolate_, object, kStartAtReceiver, end);
253       !iter.IsAtEnd();) {
254    // Start the shadow checks only after the first prototype has added
255    // shadowing keys.
256    if (HasShadowingKeys()) skip_shadow_check_ = false;
257    Handle<JSReceiver> current =
258        PrototypeIterator::GetCurrent<JSReceiver>(iter);
259    Maybe<bool> result = Just(false);  // Dummy initialization.
260    if (current->IsJSProxy()) {
261      result = CollectOwnJSProxyKeys(receiver, Handle<JSProxy>::cast(current));
262    } else {
263      DCHECK(current->IsJSObject());
264      result = CollectOwnKeys(receiver, Handle<JSObject>::cast(current));
265    }
266    MAYBE_RETURN(result, Nothing<bool>());
267    if (!result.FromJust()) break;  // |false| means "stop iterating".
268    // Iterate through proxies but ignore access checks for the ALL_CAN_READ
269    // case on API objects for OWN_ONLY keys handled in CollectOwnKeys.
270    if (!iter.AdvanceFollowingProxiesIgnoringAccessChecks()) {
271      return Nothing<bool>();
272    }
273    if (!last_non_empty_prototype_.is_null() &&
274        *last_non_empty_prototype_ == *current) {
275      break;
276    }
277  }
278  return Just(true);
279}
280
281bool KeyAccumulator::HasShadowingKeys() { return !shadowing_keys_.is_null(); }
282
283bool KeyAccumulator::IsShadowed(Handle<Object> key) {
284  if (!HasShadowingKeys() || skip_shadow_check_) return false;
285  return shadowing_keys_->Has(isolate_, key);
286}
287
288void KeyAccumulator::AddShadowingKey(Object key,
289                                     AllowGarbageCollection* allow_gc) {
290  if (mode_ == KeyCollectionMode::kOwnOnly) return;
291  AddShadowingKey(handle(key, isolate_));
292}
293void KeyAccumulator::AddShadowingKey(Handle<Object> key) {
294  if (mode_ == KeyCollectionMode::kOwnOnly) return;
295  if (shadowing_keys_.is_null()) {
296    shadowing_keys_ = ObjectHashSet::New(isolate_, 16);
297  }
298  shadowing_keys_ = ObjectHashSet::Add(isolate(), shadowing_keys_, key);
299}
300
301namespace {
302
303void TrySettingEmptyEnumCache(JSReceiver object) {
304  Map map = object.map();
305  DCHECK_EQ(kInvalidEnumCacheSentinel, map.EnumLength());
306  if (!map.OnlyHasSimpleProperties()) return;
307  if (map.IsJSProxyMap()) return;
308  if (map.NumberOfEnumerableProperties() > 0) return;
309  DCHECK(object.IsJSObject());
310  map.SetEnumLength(0);
311}
312
313bool CheckAndInitalizeEmptyEnumCache(JSReceiver object) {
314  if (object.map().EnumLength() == kInvalidEnumCacheSentinel) {
315    TrySettingEmptyEnumCache(object);
316  }
317  if (object.map().EnumLength() != 0) return false;
318  DCHECK(object.IsJSObject());
319  return !JSObject::cast(object).HasEnumerableElements();
320}
321}  // namespace
322
323void FastKeyAccumulator::Prepare() {
324  DisallowGarbageCollection no_gc;
325  // Directly go for the fast path for OWN_ONLY keys.
326  if (mode_ == KeyCollectionMode::kOwnOnly) return;
327  // Fully walk the prototype chain and find the last prototype with keys.
328  is_receiver_simple_enum_ = false;
329  has_empty_prototype_ = true;
330  only_own_has_simple_elements_ =
331      !receiver_->map().IsCustomElementsReceiverMap();
332  JSReceiver last_prototype;
333  may_have_elements_ = MayHaveElements(*receiver_);
334  for (PrototypeIterator iter(isolate_, *receiver_); !iter.IsAtEnd();
335       iter.Advance()) {
336    JSReceiver current = iter.GetCurrent<JSReceiver>();
337    if (!may_have_elements_ || only_own_has_simple_elements_) {
338      if (MayHaveElements(current)) {
339        may_have_elements_ = true;
340        only_own_has_simple_elements_ = false;
341      }
342    }
343    bool has_no_properties = CheckAndInitalizeEmptyEnumCache(current);
344    if (has_no_properties) continue;
345    last_prototype = current;
346    has_empty_prototype_ = false;
347  }
348  // Check if we should try to create/use prototype info cache.
349  try_prototype_info_cache_ = TryPrototypeInfoCache(receiver_);
350  if (has_prototype_info_cache_) return;
351  if (has_empty_prototype_) {
352    is_receiver_simple_enum_ =
353        receiver_->map().EnumLength() != kInvalidEnumCacheSentinel &&
354        !JSObject::cast(*receiver_).HasEnumerableElements();
355  } else if (!last_prototype.is_null()) {
356    last_non_empty_prototype_ = handle(last_prototype, isolate_);
357  }
358}
359
360namespace {
361
362Handle<FixedArray> ReduceFixedArrayTo(Isolate* isolate,
363                                      Handle<FixedArray> array, int length) {
364  DCHECK_LE(length, array->length());
365  if (array->length() == length) return array;
366  return isolate->factory()->CopyFixedArrayUpTo(array, length);
367}
368
369// Initializes and directly returns the enume cache. Users of this function
370// have to make sure to never directly leak the enum cache.
371Handle<FixedArray> GetFastEnumPropertyKeys(Isolate* isolate,
372                                           Handle<JSObject> object) {
373  Handle<Map> map(object->map(), isolate);
374  Handle<FixedArray> keys(
375      map->instance_descriptors(isolate).enum_cache().keys(), isolate);
376
377  // Check if the {map} has a valid enum length, which implies that it
378  // must have a valid enum cache as well.
379  int enum_length = map->EnumLength();
380  if (enum_length != kInvalidEnumCacheSentinel) {
381    DCHECK(map->OnlyHasSimpleProperties());
382    DCHECK_LE(enum_length, keys->length());
383    DCHECK_EQ(enum_length, map->NumberOfEnumerableProperties());
384    isolate->counters()->enum_cache_hits()->Increment();
385    return ReduceFixedArrayTo(isolate, keys, enum_length);
386  }
387
388  // Determine the actual number of enumerable properties of the {map}.
389  enum_length = map->NumberOfEnumerableProperties();
390
391  // Check if there's already a shared enum cache on the {map}s
392  // DescriptorArray with sufficient number of entries.
393  if (enum_length <= keys->length()) {
394    if (map->OnlyHasSimpleProperties()) map->SetEnumLength(enum_length);
395    isolate->counters()->enum_cache_hits()->Increment();
396    return ReduceFixedArrayTo(isolate, keys, enum_length);
397  }
398
399  Handle<DescriptorArray> descriptors =
400      Handle<DescriptorArray>(map->instance_descriptors(isolate), isolate);
401  isolate->counters()->enum_cache_misses()->Increment();
402
403  // Create the keys array.
404  int index = 0;
405  bool fields_only = true;
406  keys = isolate->factory()->NewFixedArray(enum_length);
407  for (InternalIndex i : map->IterateOwnDescriptors()) {
408    DisallowGarbageCollection no_gc;
409    PropertyDetails details = descriptors->GetDetails(i);
410    if (details.IsDontEnum()) continue;
411    Object key = descriptors->GetKey(i);
412    if (key.IsSymbol()) continue;
413    keys->set(index, key);
414    if (details.location() != PropertyLocation::kField) fields_only = false;
415    index++;
416  }
417  DCHECK_EQ(index, keys->length());
418
419  // Optionally also create the indices array.
420  Handle<FixedArray> indices = isolate->factory()->empty_fixed_array();
421  if (fields_only) {
422    indices = isolate->factory()->NewFixedArray(enum_length);
423    index = 0;
424    for (InternalIndex i : map->IterateOwnDescriptors()) {
425      DisallowGarbageCollection no_gc;
426      PropertyDetails details = descriptors->GetDetails(i);
427      if (details.IsDontEnum()) continue;
428      Object key = descriptors->GetKey(i);
429      if (key.IsSymbol()) continue;
430      DCHECK_EQ(PropertyKind::kData, details.kind());
431      DCHECK_EQ(PropertyLocation::kField, details.location());
432      FieldIndex field_index = FieldIndex::ForDescriptor(*map, i);
433      indices->set(index, Smi::FromInt(field_index.GetLoadByFieldIndex()));
434      index++;
435    }
436    DCHECK_EQ(index, indices->length());
437  }
438
439  DescriptorArray::InitializeOrChangeEnumCache(descriptors, isolate, keys,
440                                               indices);
441  if (map->OnlyHasSimpleProperties()) map->SetEnumLength(enum_length);
442
443  return keys;
444}
445
446template <bool fast_properties>
447MaybeHandle<FixedArray> GetOwnKeysWithElements(Isolate* isolate,
448                                               Handle<JSObject> object,
449                                               GetKeysConversion convert,
450                                               bool skip_indices) {
451  Handle<FixedArray> keys;
452  ElementsAccessor* accessor = object->GetElementsAccessor();
453  if (fast_properties) {
454    keys = GetFastEnumPropertyKeys(isolate, object);
455  } else {
456    // TODO(cbruni): preallocate big enough array to also hold elements.
457    keys = KeyAccumulator::GetOwnEnumPropertyKeys(isolate, object);
458  }
459
460  MaybeHandle<FixedArray> result;
461  if (skip_indices) {
462    result = keys;
463  } else {
464    result =
465        accessor->PrependElementIndices(object, keys, convert, ONLY_ENUMERABLE);
466  }
467
468  if (FLAG_trace_for_in_enumerate) {
469    PrintF("| strings=%d symbols=0 elements=%u || prototypes>=1 ||\n",
470           keys->length(), result.ToHandleChecked()->length() - keys->length());
471  }
472  return result;
473}
474
475}  // namespace
476
477MaybeHandle<FixedArray> FastKeyAccumulator::GetKeys(
478    GetKeysConversion keys_conversion) {
479  // TODO(v8:9401): We should extend the fast path of KeyAccumulator::GetKeys to
480  // also use fast path even when filter = SKIP_SYMBOLS. We used to pass wrong
481  // filter to use fast path in cases where we tried to verify all properties
482  // are enumerable. However these checks weren't correct and passing the wrong
483  // filter led to wrong behaviour.
484  if (filter_ == ENUMERABLE_STRINGS) {
485    Handle<FixedArray> keys;
486    if (GetKeysFast(keys_conversion).ToHandle(&keys)) {
487      return keys;
488    }
489    if (isolate_->has_pending_exception()) return MaybeHandle<FixedArray>();
490  }
491
492  if (try_prototype_info_cache_) {
493    return GetKeysWithPrototypeInfoCache(keys_conversion);
494  }
495  return GetKeysSlow(keys_conversion);
496}
497
498MaybeHandle<FixedArray> FastKeyAccumulator::GetKeysFast(
499    GetKeysConversion keys_conversion) {
500  bool own_only = has_empty_prototype_ || mode_ == KeyCollectionMode::kOwnOnly;
501  Map map = receiver_->map();
502  if (!own_only || map.IsCustomElementsReceiverMap()) {
503    return MaybeHandle<FixedArray>();
504  }
505
506  // From this point on we are certain to only collect own keys.
507  DCHECK(receiver_->IsJSObject());
508  Handle<JSObject> object = Handle<JSObject>::cast(receiver_);
509
510  // Do not try to use the enum-cache for dict-mode objects.
511  if (map.is_dictionary_map()) {
512    return GetOwnKeysWithElements<false>(isolate_, object, keys_conversion,
513                                         skip_indices_);
514  }
515  int enum_length = receiver_->map().EnumLength();
516  if (enum_length == kInvalidEnumCacheSentinel) {
517    Handle<FixedArray> keys;
518    // Try initializing the enum cache and return own properties.
519    if (GetOwnKeysWithUninitializedEnumCache().ToHandle(&keys)) {
520      if (FLAG_trace_for_in_enumerate) {
521        PrintF("| strings=%d symbols=0 elements=0 || prototypes>=1 ||\n",
522               keys->length());
523      }
524      is_receiver_simple_enum_ =
525          object->map().EnumLength() != kInvalidEnumCacheSentinel;
526      return keys;
527    }
528  }
529  // The properties-only case failed because there were probably elements on the
530  // receiver.
531  return GetOwnKeysWithElements<true>(isolate_, object, keys_conversion,
532                                      skip_indices_);
533}
534
535MaybeHandle<FixedArray>
536FastKeyAccumulator::GetOwnKeysWithUninitializedEnumCache() {
537  Handle<JSObject> object = Handle<JSObject>::cast(receiver_);
538  // Uninitalized enum cache
539  Map map = object->map();
540  if (object->elements() != ReadOnlyRoots(isolate_).empty_fixed_array() &&
541      object->elements() !=
542          ReadOnlyRoots(isolate_).empty_slow_element_dictionary()) {
543    // Assume that there are elements.
544    return MaybeHandle<FixedArray>();
545  }
546  int number_of_own_descriptors = map.NumberOfOwnDescriptors();
547  if (number_of_own_descriptors == 0) {
548    map.SetEnumLength(0);
549    return isolate_->factory()->empty_fixed_array();
550  }
551  // We have no elements but possibly enumerable property keys, hence we can
552  // directly initialize the enum cache.
553  Handle<FixedArray> keys = GetFastEnumPropertyKeys(isolate_, object);
554  if (is_for_in_) return keys;
555  // Do not leak the enum cache as it might end up as an elements backing store.
556  return isolate_->factory()->CopyFixedArray(keys);
557}
558
559MaybeHandle<FixedArray> FastKeyAccumulator::GetKeysSlow(
560    GetKeysConversion keys_conversion) {
561  KeyAccumulator accumulator(isolate_, mode_, filter_);
562  accumulator.set_is_for_in(is_for_in_);
563  accumulator.set_skip_indices(skip_indices_);
564  accumulator.set_last_non_empty_prototype(last_non_empty_prototype_);
565  accumulator.set_may_have_elements(may_have_elements_);
566  accumulator.set_first_prototype_map(first_prototype_map_);
567  accumulator.set_try_prototype_info_cache(try_prototype_info_cache_);
568
569  MAYBE_RETURN(accumulator.CollectKeys(receiver_, receiver_),
570               MaybeHandle<FixedArray>());
571  return accumulator.GetKeys(keys_conversion);
572}
573
574MaybeHandle<FixedArray> FastKeyAccumulator::GetKeysWithPrototypeInfoCache(
575    GetKeysConversion keys_conversion) {
576  Handle<FixedArray> own_keys;
577  if (may_have_elements_) {
578    MaybeHandle<FixedArray> maybe_own_keys;
579    if (receiver_->map().is_dictionary_map()) {
580      maybe_own_keys = GetOwnKeysWithElements<false>(
581          isolate_, Handle<JSObject>::cast(receiver_), keys_conversion,
582          skip_indices_);
583    } else {
584      maybe_own_keys = GetOwnKeysWithElements<true>(
585          isolate_, Handle<JSObject>::cast(receiver_), keys_conversion,
586          skip_indices_);
587    }
588    ASSIGN_RETURN_ON_EXCEPTION(isolate_, own_keys, maybe_own_keys, FixedArray);
589  } else {
590    own_keys = KeyAccumulator::GetOwnEnumPropertyKeys(
591        isolate_, Handle<JSObject>::cast(receiver_));
592  }
593  Handle<FixedArray> prototype_chain_keys;
594  if (has_prototype_info_cache_) {
595    prototype_chain_keys =
596        handle(FixedArray::cast(
597                   PrototypeInfo::cast(first_prototype_map_->prototype_info())
598                       .prototype_chain_enum_cache()),
599               isolate_);
600  } else {
601    KeyAccumulator accumulator(isolate_, mode_, filter_);
602    accumulator.set_is_for_in(is_for_in_);
603    accumulator.set_skip_indices(skip_indices_);
604    accumulator.set_last_non_empty_prototype(last_non_empty_prototype_);
605    accumulator.set_may_have_elements(may_have_elements_);
606    accumulator.set_receiver(receiver_);
607    accumulator.set_first_prototype_map(first_prototype_map_);
608    accumulator.set_try_prototype_info_cache(try_prototype_info_cache_);
609    MAYBE_RETURN(accumulator.CollectKeys(first_prototype_, first_prototype_),
610                 MaybeHandle<FixedArray>());
611    prototype_chain_keys = accumulator.GetKeys(keys_conversion);
612  }
613  Handle<FixedArray> result = CombineKeys(
614      isolate_, own_keys, prototype_chain_keys, receiver_, may_have_elements_);
615  if (is_for_in_ && own_keys.is_identical_to(result)) {
616    // Don't leak the enumeration cache without the receiver since it might get
617    // trimmed otherwise.
618    return isolate_->factory()->CopyFixedArrayUpTo(result, result->length());
619  }
620  return result;
621}
622
623bool FastKeyAccumulator::MayHaveElements(JSReceiver receiver) {
624  if (!receiver.IsJSObject()) return true;
625  JSObject object = JSObject::cast(receiver);
626  if (object.HasEnumerableElements()) return true;
627  if (object.HasIndexedInterceptor()) return true;
628  return false;
629}
630
631bool FastKeyAccumulator::TryPrototypeInfoCache(Handle<JSReceiver> receiver) {
632  if (may_have_elements_ && !only_own_has_simple_elements_) return false;
633  Handle<JSObject> object = Handle<JSObject>::cast(receiver);
634  if (!object->HasFastProperties()) return false;
635  if (object->HasNamedInterceptor()) return false;
636  if (object->IsAccessCheckNeeded() &&
637      !isolate_->MayAccess(handle(isolate_->context(), isolate_), object)) {
638    return false;
639  }
640  HeapObject prototype = receiver->map().prototype();
641  if (prototype.is_null()) return false;
642  if (!prototype.map().is_prototype_map() ||
643      !prototype.map().prototype_info().IsPrototypeInfo()) {
644    return false;
645  }
646  first_prototype_ = handle(JSReceiver::cast(prototype), isolate_);
647  Handle<Map> map(prototype.map(), isolate_);
648  first_prototype_map_ = map;
649  has_prototype_info_cache_ = map->IsPrototypeValidityCellValid() &&
650                              PrototypeInfo::cast(map->prototype_info())
651                                  .prototype_chain_enum_cache()
652                                  .IsFixedArray();
653  return true;
654}
655
656V8_WARN_UNUSED_RESULT ExceptionStatus
657KeyAccumulator::FilterForEnumerableProperties(
658    Handle<JSReceiver> receiver, Handle<JSObject> object,
659    Handle<InterceptorInfo> interceptor, Handle<JSObject> result,
660    IndexedOrNamed type) {
661  DCHECK(result->IsJSArray() || result->HasSloppyArgumentsElements());
662  ElementsAccessor* accessor = result->GetElementsAccessor();
663
664  size_t length = accessor->GetCapacity(*result, result->elements());
665  for (InternalIndex entry : InternalIndex::Range(length)) {
666    if (!accessor->HasEntry(*result, entry)) continue;
667
668    // args are invalid after args.Call(), create a new one in every iteration.
669    PropertyCallbackArguments args(isolate_, interceptor->data(), *receiver,
670                                   *object, Just(kDontThrow));
671
672    Handle<Object> element = accessor->Get(result, entry);
673    Handle<Object> attributes;
674    if (type == kIndexed) {
675      uint32_t number;
676      CHECK(element->ToUint32(&number));
677      attributes = args.CallIndexedQuery(interceptor, number);
678    } else {
679      CHECK(element->IsName());
680      attributes =
681          args.CallNamedQuery(interceptor, Handle<Name>::cast(element));
682    }
683
684    if (!attributes.is_null()) {
685      int32_t value;
686      CHECK(attributes->ToInt32(&value));
687      if ((value & DONT_ENUM) == 0) {
688        RETURN_FAILURE_IF_NOT_SUCCESSFUL(AddKey(element, DO_NOT_CONVERT));
689      }
690    }
691  }
692  return ExceptionStatus::kSuccess;
693}
694
695// Returns |true| on success, |nothing| on exception.
696Maybe<bool> KeyAccumulator::CollectInterceptorKeysInternal(
697    Handle<JSReceiver> receiver, Handle<JSObject> object,
698    Handle<InterceptorInfo> interceptor, IndexedOrNamed type) {
699  PropertyCallbackArguments enum_args(isolate_, interceptor->data(), *receiver,
700                                      *object, Just(kDontThrow));
701
702  Handle<JSObject> result;
703  if (!interceptor->enumerator().IsUndefined(isolate_)) {
704    if (type == kIndexed) {
705      result = enum_args.CallIndexedEnumerator(interceptor);
706    } else {
707      DCHECK_EQ(type, kNamed);
708      result = enum_args.CallNamedEnumerator(interceptor);
709    }
710  }
711  RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate_, Nothing<bool>());
712  if (result.is_null()) return Just(true);
713
714  if ((filter_ & ONLY_ENUMERABLE) &&
715      !interceptor->query().IsUndefined(isolate_)) {
716    RETURN_NOTHING_IF_NOT_SUCCESSFUL(FilterForEnumerableProperties(
717        receiver, object, interceptor, result, type));
718  } else {
719    RETURN_NOTHING_IF_NOT_SUCCESSFUL(AddKeys(
720        result, type == kIndexed ? CONVERT_TO_ARRAY_INDEX : DO_NOT_CONVERT));
721  }
722  return Just(true);
723}
724
725Maybe<bool> KeyAccumulator::CollectInterceptorKeys(Handle<JSReceiver> receiver,
726                                                   Handle<JSObject> object,
727                                                   IndexedOrNamed type) {
728  if (type == kIndexed) {
729    if (!object->HasIndexedInterceptor()) return Just(true);
730  } else {
731    if (!object->HasNamedInterceptor()) return Just(true);
732  }
733  Handle<InterceptorInfo> interceptor(type == kIndexed
734                                          ? object->GetIndexedInterceptor()
735                                          : object->GetNamedInterceptor(),
736                                      isolate_);
737  if ((filter() & ONLY_ALL_CAN_READ) && !interceptor->all_can_read()) {
738    return Just(true);
739  }
740  return CollectInterceptorKeysInternal(receiver, object, interceptor, type);
741}
742
743Maybe<bool> KeyAccumulator::CollectOwnElementIndices(
744    Handle<JSReceiver> receiver, Handle<JSObject> object) {
745  if (filter_ & SKIP_STRINGS || skip_indices_) return Just(true);
746
747  ElementsAccessor* accessor = object->GetElementsAccessor();
748  RETURN_NOTHING_IF_NOT_SUCCESSFUL(
749      accessor->CollectElementIndices(object, this));
750  return CollectInterceptorKeys(receiver, object, kIndexed);
751}
752
753namespace {
754
755template <bool skip_symbols>
756base::Optional<int> CollectOwnPropertyNamesInternal(
757    Handle<JSObject> object, KeyAccumulator* keys,
758    Handle<DescriptorArray> descs, int start_index, int limit) {
759  AllowGarbageCollection allow_gc;
760  int first_skipped = -1;
761  PropertyFilter filter = keys->filter();
762  KeyCollectionMode mode = keys->mode();
763  for (InternalIndex i : InternalIndex::Range(start_index, limit)) {
764    bool is_shadowing_key = false;
765    PropertyDetails details = descs->GetDetails(i);
766
767    if ((details.attributes() & filter) != 0) {
768      if (mode == KeyCollectionMode::kIncludePrototypes) {
769        is_shadowing_key = true;
770      } else {
771        continue;
772      }
773    }
774
775    if (filter & ONLY_ALL_CAN_READ) {
776      if (details.kind() != PropertyKind::kAccessor) continue;
777      Object accessors = descs->GetStrongValue(i);
778      if (!accessors.IsAccessorInfo()) continue;
779      if (!AccessorInfo::cast(accessors).all_can_read()) continue;
780    }
781
782    Name key = descs->GetKey(i);
783    if (skip_symbols == key.IsSymbol()) {
784      if (first_skipped == -1) first_skipped = i.as_int();
785      continue;
786    }
787    if (key.FilterKey(keys->filter())) continue;
788
789    if (is_shadowing_key) {
790      // This might allocate, but {key} is not used afterwards.
791      keys->AddShadowingKey(key, &allow_gc);
792      continue;
793    } else {
794      if (keys->AddKey(key, DO_NOT_CONVERT) != ExceptionStatus::kSuccess) {
795        return base::Optional<int>();
796      }
797    }
798  }
799  return first_skipped;
800}
801
802// Logic shared between different specializations of CopyEnumKeysTo.
803template <typename Dictionary>
804void CommonCopyEnumKeysTo(Isolate* isolate, Handle<Dictionary> dictionary,
805                          Handle<FixedArray> storage, KeyCollectionMode mode,
806                          KeyAccumulator* accumulator) {
807  DCHECK_IMPLIES(mode != KeyCollectionMode::kOwnOnly, accumulator != nullptr);
808  int length = storage->length();
809  int properties = 0;
810  ReadOnlyRoots roots(isolate);
811
812  AllowGarbageCollection allow_gc;
813  for (InternalIndex i : dictionary->IterateEntries()) {
814    Object key;
815    if (!dictionary->ToKey(roots, i, &key)) continue;
816    bool is_shadowing_key = false;
817    if (key.IsSymbol()) continue;
818    PropertyDetails details = dictionary->DetailsAt(i);
819    if (details.IsDontEnum()) {
820      if (mode == KeyCollectionMode::kIncludePrototypes) {
821        is_shadowing_key = true;
822      } else {
823        continue;
824      }
825    }
826    if (is_shadowing_key) {
827      // This might allocate, but {key} is not used afterwards.
828      accumulator->AddShadowingKey(key, &allow_gc);
829      continue;
830    } else {
831      if (Dictionary::kIsOrderedDictionaryType) {
832        storage->set(properties, Name::cast(key));
833      } else {
834        // If the dictionary does not store elements in enumeration order,
835        // we need to sort it afterwards in CopyEnumKeysTo. To enable this we
836        // need to store indices at this point, rather than the values at the
837        // given indices.
838        storage->set(properties, Smi::FromInt(i.as_int()));
839      }
840    }
841    properties++;
842    if (mode == KeyCollectionMode::kOwnOnly && properties == length) break;
843  }
844
845  CHECK_EQ(length, properties);
846}
847
848// Copies enumerable keys to preallocated fixed array.
849// Does not throw for uninitialized exports in module namespace objects, so
850// this has to be checked separately.
851template <typename Dictionary>
852void CopyEnumKeysTo(Isolate* isolate, Handle<Dictionary> dictionary,
853                    Handle<FixedArray> storage, KeyCollectionMode mode,
854                    KeyAccumulator* accumulator) {
855  STATIC_ASSERT(!Dictionary::kIsOrderedDictionaryType);
856
857  CommonCopyEnumKeysTo<Dictionary>(isolate, dictionary, storage, mode,
858                                   accumulator);
859
860  int length = storage->length();
861
862  DisallowGarbageCollection no_gc;
863  Dictionary raw_dictionary = *dictionary;
864  FixedArray raw_storage = *storage;
865  EnumIndexComparator<Dictionary> cmp(raw_dictionary);
866  // Use AtomicSlot wrapper to ensure that std::sort uses atomic load and
867  // store operations that are safe for concurrent marking.
868  AtomicSlot start(storage->GetFirstElementAddress());
869  std::sort(start, start + length, cmp);
870  for (int i = 0; i < length; i++) {
871    InternalIndex index(Smi::ToInt(raw_storage.get(i)));
872    raw_storage.set(i, raw_dictionary.NameAt(index));
873  }
874}
875
876template <>
877void CopyEnumKeysTo(Isolate* isolate, Handle<SwissNameDictionary> dictionary,
878                    Handle<FixedArray> storage, KeyCollectionMode mode,
879                    KeyAccumulator* accumulator) {
880  CommonCopyEnumKeysTo<SwissNameDictionary>(isolate, dictionary, storage, mode,
881                                            accumulator);
882
883  // No need to sort, as CommonCopyEnumKeysTo on OrderedNameDictionary
884  // adds entries to |storage| in the dict's insertion order
885  // Further, the template argument true above means that |storage|
886  // now contains the actual values from |dictionary|, rather than indices.
887}
888
889template <class T>
890Handle<FixedArray> GetOwnEnumPropertyDictionaryKeys(Isolate* isolate,
891                                                    KeyCollectionMode mode,
892                                                    KeyAccumulator* accumulator,
893                                                    Handle<JSObject> object,
894                                                    T raw_dictionary) {
895  Handle<T> dictionary(raw_dictionary, isolate);
896  if (dictionary->NumberOfElements() == 0) {
897    return isolate->factory()->empty_fixed_array();
898  }
899  int length = dictionary->NumberOfEnumerableProperties();
900  Handle<FixedArray> storage = isolate->factory()->NewFixedArray(length);
901  CopyEnumKeysTo(isolate, dictionary, storage, mode, accumulator);
902  return storage;
903}
904
905// Collect the keys from |dictionary| into |keys|, in ascending chronological
906// order of property creation.
907template <typename Dictionary>
908ExceptionStatus CollectKeysFromDictionary(Handle<Dictionary> dictionary,
909                                          KeyAccumulator* keys) {
910  Isolate* isolate = keys->isolate();
911  ReadOnlyRoots roots(isolate);
912  // TODO(jkummerow): Consider using a std::unique_ptr<InternalIndex[]> instead.
913  Handle<FixedArray> array =
914      isolate->factory()->NewFixedArray(dictionary->NumberOfElements());
915  int array_size = 0;
916  PropertyFilter filter = keys->filter();
917  // Handle enumerable strings in CopyEnumKeysTo.
918  DCHECK_NE(keys->filter(), ENUMERABLE_STRINGS);
919  {
920    DisallowGarbageCollection no_gc;
921    for (InternalIndex i : dictionary->IterateEntries()) {
922      Object key;
923      Dictionary raw_dictionary = *dictionary;
924      if (!raw_dictionary.ToKey(roots, i, &key)) continue;
925      if (key.FilterKey(filter)) continue;
926      PropertyDetails details = raw_dictionary.DetailsAt(i);
927      if ((details.attributes() & filter) != 0) {
928        AllowGarbageCollection gc;
929        // This might allocate, but {key} is not used afterwards.
930        keys->AddShadowingKey(key, &gc);
931        continue;
932      }
933      if (filter & ONLY_ALL_CAN_READ) {
934        if (details.kind() != PropertyKind::kAccessor) continue;
935        Object accessors = raw_dictionary.ValueAt(i);
936        if (!accessors.IsAccessorInfo()) continue;
937        if (!AccessorInfo::cast(accessors).all_can_read()) continue;
938      }
939      // TODO(emrich): consider storing keys instead of indices into the array
940      // in case of ordered dictionary type.
941      array->set(array_size++, Smi::FromInt(i.as_int()));
942    }
943    if (!Dictionary::kIsOrderedDictionaryType) {
944      // Sorting only needed if it's an unordered dictionary,
945      // otherwise we traversed elements in insertion order
946
947      EnumIndexComparator<Dictionary> cmp(*dictionary);
948      // Use AtomicSlot wrapper to ensure that std::sort uses atomic load and
949      // store operations that are safe for concurrent marking.
950      AtomicSlot start(array->GetFirstElementAddress());
951      std::sort(start, start + array_size, cmp);
952    }
953  }
954
955  bool has_seen_symbol = false;
956  for (int i = 0; i < array_size; i++) {
957    InternalIndex index(Smi::ToInt(array->get(i)));
958    Object key = dictionary->NameAt(index);
959    if (key.IsSymbol()) {
960      has_seen_symbol = true;
961      continue;
962    }
963    ExceptionStatus status = keys->AddKey(key, DO_NOT_CONVERT);
964    if (!status) return status;
965  }
966  if (has_seen_symbol) {
967    for (int i = 0; i < array_size; i++) {
968      InternalIndex index(Smi::ToInt(array->get(i)));
969      Object key = dictionary->NameAt(index);
970      if (!key.IsSymbol()) continue;
971      ExceptionStatus status = keys->AddKey(key, DO_NOT_CONVERT);
972      if (!status) return status;
973    }
974  }
975  return ExceptionStatus::kSuccess;
976}
977
978}  // namespace
979
980Maybe<bool> KeyAccumulator::CollectOwnPropertyNames(Handle<JSReceiver> receiver,
981                                                    Handle<JSObject> object) {
982  if (filter_ == ENUMERABLE_STRINGS) {
983    Handle<FixedArray> enum_keys;
984    if (object->HasFastProperties()) {
985      enum_keys = KeyAccumulator::GetOwnEnumPropertyKeys(isolate_, object);
986      // If the number of properties equals the length of enumerable properties
987      // we do not have to filter out non-enumerable ones
988      Map map = object->map();
989      int nof_descriptors = map.NumberOfOwnDescriptors();
990      if (enum_keys->length() != nof_descriptors) {
991        if (map.prototype(isolate_) != ReadOnlyRoots(isolate_).null_value()) {
992          AllowGarbageCollection allow_gc;
993          Handle<DescriptorArray> descs = Handle<DescriptorArray>(
994              map.instance_descriptors(isolate_), isolate_);
995          for (InternalIndex i : InternalIndex::Range(nof_descriptors)) {
996            PropertyDetails details = descs->GetDetails(i);
997            if (!details.IsDontEnum()) continue;
998            this->AddShadowingKey(descs->GetKey(i), &allow_gc);
999          }
1000        }
1001      }
1002    } else if (object->IsJSGlobalObject()) {
1003      enum_keys = GetOwnEnumPropertyDictionaryKeys(
1004          isolate_, mode_, this, object,
1005          JSGlobalObject::cast(*object).global_dictionary(kAcquireLoad));
1006    } else if (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) {
1007      enum_keys = GetOwnEnumPropertyDictionaryKeys(
1008          isolate_, mode_, this, object, object->property_dictionary_swiss());
1009    } else {
1010      enum_keys = GetOwnEnumPropertyDictionaryKeys(
1011          isolate_, mode_, this, object, object->property_dictionary());
1012    }
1013    if (object->IsJSModuleNamespace()) {
1014      // Simulate [[GetOwnProperty]] for establishing enumerability, which
1015      // throws for uninitialized exports.
1016      for (int i = 0, n = enum_keys->length(); i < n; ++i) {
1017        Handle<String> key(String::cast(enum_keys->get(i)), isolate_);
1018        if (Handle<JSModuleNamespace>::cast(object)
1019                ->GetExport(isolate(), key)
1020                .is_null()) {
1021          return Nothing<bool>();
1022        }
1023      }
1024    }
1025    RETURN_NOTHING_IF_NOT_SUCCESSFUL(AddKeys(enum_keys, DO_NOT_CONVERT));
1026  } else {
1027    if (object->HasFastProperties()) {
1028      int limit = object->map().NumberOfOwnDescriptors();
1029      Handle<DescriptorArray> descs(
1030          object->map().instance_descriptors(isolate_), isolate_);
1031      // First collect the strings,
1032      base::Optional<int> first_symbol =
1033          CollectOwnPropertyNamesInternal<true>(object, this, descs, 0, limit);
1034      // then the symbols.
1035      RETURN_NOTHING_IF_NOT_SUCCESSFUL(first_symbol);
1036      if (first_symbol.value() != -1) {
1037        RETURN_NOTHING_IF_NOT_SUCCESSFUL(CollectOwnPropertyNamesInternal<false>(
1038            object, this, descs, first_symbol.value(), limit));
1039      }
1040    } else if (object->IsJSGlobalObject()) {
1041      RETURN_NOTHING_IF_NOT_SUCCESSFUL(CollectKeysFromDictionary(
1042          handle(JSGlobalObject::cast(*object).global_dictionary(kAcquireLoad),
1043                 isolate_),
1044          this));
1045    } else if (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) {
1046      RETURN_NOTHING_IF_NOT_SUCCESSFUL(CollectKeysFromDictionary(
1047          handle(object->property_dictionary_swiss(), isolate_), this));
1048    } else {
1049      RETURN_NOTHING_IF_NOT_SUCCESSFUL(CollectKeysFromDictionary(
1050          handle(object->property_dictionary(), isolate_), this));
1051    }
1052  }
1053  // Add the property keys from the interceptor.
1054  return CollectInterceptorKeys(receiver, object, kNamed);
1055}
1056
1057ExceptionStatus KeyAccumulator::CollectPrivateNames(Handle<JSReceiver> receiver,
1058                                                    Handle<JSObject> object) {
1059  DCHECK_EQ(mode_, KeyCollectionMode::kOwnOnly);
1060  if (object->HasFastProperties()) {
1061    int limit = object->map().NumberOfOwnDescriptors();
1062    Handle<DescriptorArray> descs(object->map().instance_descriptors(isolate_),
1063                                  isolate_);
1064    CollectOwnPropertyNamesInternal<false>(object, this, descs, 0, limit);
1065  } else if (object->IsJSGlobalObject()) {
1066    RETURN_FAILURE_IF_NOT_SUCCESSFUL(CollectKeysFromDictionary(
1067        handle(JSGlobalObject::cast(*object).global_dictionary(kAcquireLoad),
1068               isolate_),
1069        this));
1070  } else if (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) {
1071    RETURN_FAILURE_IF_NOT_SUCCESSFUL(CollectKeysFromDictionary(
1072        handle(object->property_dictionary_swiss(), isolate_), this));
1073  } else {
1074    RETURN_FAILURE_IF_NOT_SUCCESSFUL(CollectKeysFromDictionary(
1075        handle(object->property_dictionary(), isolate_), this));
1076  }
1077  return ExceptionStatus::kSuccess;
1078}
1079
1080Maybe<bool> KeyAccumulator::CollectAccessCheckInterceptorKeys(
1081    Handle<AccessCheckInfo> access_check_info, Handle<JSReceiver> receiver,
1082    Handle<JSObject> object) {
1083  if (!skip_indices_) {
1084    MAYBE_RETURN((CollectInterceptorKeysInternal(
1085                     receiver, object,
1086                     handle(InterceptorInfo::cast(
1087                                access_check_info->indexed_interceptor()),
1088                            isolate_),
1089                     kIndexed)),
1090                 Nothing<bool>());
1091  }
1092  MAYBE_RETURN(
1093      (CollectInterceptorKeysInternal(
1094          receiver, object,
1095          handle(InterceptorInfo::cast(access_check_info->named_interceptor()),
1096                 isolate_),
1097          kNamed)),
1098      Nothing<bool>());
1099  return Just(true);
1100}
1101
1102// Returns |true| on success, |false| if prototype walking should be stopped,
1103// |nothing| if an exception was thrown.
1104Maybe<bool> KeyAccumulator::CollectOwnKeys(Handle<JSReceiver> receiver,
1105                                           Handle<JSObject> object) {
1106  // Check access rights if required.
1107  if (object->IsAccessCheckNeeded() &&
1108      !isolate_->MayAccess(handle(isolate_->context(), isolate_), object)) {
1109    // The cross-origin spec says that [[Enumerate]] shall return an empty
1110    // iterator when it doesn't have access...
1111    if (mode_ == KeyCollectionMode::kIncludePrototypes) {
1112      return Just(false);
1113    }
1114    // ...whereas [[OwnPropertyKeys]] shall return allowlisted properties.
1115    DCHECK_EQ(KeyCollectionMode::kOwnOnly, mode_);
1116    Handle<AccessCheckInfo> access_check_info;
1117    {
1118      DisallowGarbageCollection no_gc;
1119      AccessCheckInfo maybe_info = AccessCheckInfo::Get(isolate_, object);
1120      if (!maybe_info.is_null()) {
1121        access_check_info = handle(maybe_info, isolate_);
1122      }
1123    }
1124    // We always have both kinds of interceptors or none.
1125    if (!access_check_info.is_null() &&
1126        access_check_info->named_interceptor() != Object()) {
1127      MAYBE_RETURN(CollectAccessCheckInterceptorKeys(access_check_info,
1128                                                     receiver, object),
1129                   Nothing<bool>());
1130      return Just(false);
1131    }
1132    filter_ = static_cast<PropertyFilter>(filter_ | ONLY_ALL_CAN_READ);
1133  }
1134  if (filter_ & PRIVATE_NAMES_ONLY) {
1135    RETURN_NOTHING_IF_NOT_SUCCESSFUL(CollectPrivateNames(receiver, object));
1136    return Just(true);
1137  }
1138
1139  if (may_have_elements_) {
1140    MAYBE_RETURN(CollectOwnElementIndices(receiver, object), Nothing<bool>());
1141  }
1142  MAYBE_RETURN(CollectOwnPropertyNames(receiver, object), Nothing<bool>());
1143  return Just(true);
1144}
1145
1146// static
1147Handle<FixedArray> KeyAccumulator::GetOwnEnumPropertyKeys(
1148    Isolate* isolate, Handle<JSObject> object) {
1149  if (object->HasFastProperties()) {
1150    return GetFastEnumPropertyKeys(isolate, object);
1151  } else if (object->IsJSGlobalObject()) {
1152    return GetOwnEnumPropertyDictionaryKeys(
1153        isolate, KeyCollectionMode::kOwnOnly, nullptr, object,
1154        JSGlobalObject::cast(*object).global_dictionary(kAcquireLoad));
1155  } else if (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) {
1156    return GetOwnEnumPropertyDictionaryKeys(
1157        isolate, KeyCollectionMode::kOwnOnly, nullptr, object,
1158        object->property_dictionary_swiss());
1159  } else {
1160    return GetOwnEnumPropertyDictionaryKeys(
1161        isolate, KeyCollectionMode::kOwnOnly, nullptr, object,
1162        object->property_dictionary());
1163  }
1164}
1165
1166namespace {
1167
1168class NameComparator {
1169 public:
1170  explicit NameComparator(Isolate* isolate) : isolate_(isolate) {}
1171
1172  bool operator()(uint32_t hash1, uint32_t hash2, const Handle<Name>& key1,
1173                  const Handle<Name>& key2) const {
1174    return Name::Equals(isolate_, key1, key2);
1175  }
1176
1177 private:
1178  Isolate* isolate_;
1179};
1180
1181}  // namespace
1182
1183// ES6 #sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeys
1184// Returns |true| on success, |nothing| in case of exception.
1185Maybe<bool> KeyAccumulator::CollectOwnJSProxyKeys(Handle<JSReceiver> receiver,
1186                                                  Handle<JSProxy> proxy) {
1187  STACK_CHECK(isolate_, Nothing<bool>());
1188  if (filter_ == PRIVATE_NAMES_ONLY) {
1189    if (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) {
1190      RETURN_NOTHING_IF_NOT_SUCCESSFUL(CollectKeysFromDictionary(
1191          handle(proxy->property_dictionary_swiss(), isolate_), this));
1192    } else {
1193      RETURN_NOTHING_IF_NOT_SUCCESSFUL(CollectKeysFromDictionary(
1194          handle(proxy->property_dictionary(), isolate_), this));
1195    }
1196    return Just(true);
1197  }
1198
1199  // 1. Let handler be the value of the [[ProxyHandler]] internal slot of O.
1200  Handle<Object> handler(proxy->handler(), isolate_);
1201  // 2. If handler is null, throw a TypeError exception.
1202  // 3. Assert: Type(handler) is Object.
1203  if (proxy->IsRevoked()) {
1204    isolate_->Throw(*isolate_->factory()->NewTypeError(
1205        MessageTemplate::kProxyRevoked, isolate_->factory()->ownKeys_string()));
1206    return Nothing<bool>();
1207  }
1208  // 4. Let target be the value of the [[ProxyTarget]] internal slot of O.
1209  Handle<JSReceiver> target(JSReceiver::cast(proxy->target()), isolate_);
1210  // 5. Let trap be ? GetMethod(handler, "ownKeys").
1211  Handle<Object> trap;
1212  ASSIGN_RETURN_ON_EXCEPTION_VALUE(
1213      isolate_, trap,
1214      Object::GetMethod(Handle<JSReceiver>::cast(handler),
1215                        isolate_->factory()->ownKeys_string()),
1216      Nothing<bool>());
1217  // 6. If trap is undefined, then
1218  if (trap->IsUndefined(isolate_)) {
1219    // 6a. Return target.[[OwnPropertyKeys]]().
1220    return CollectOwnJSProxyTargetKeys(proxy, target);
1221  }
1222  // 7. Let trapResultArray be Call(trap, handler, «target»).
1223  Handle<Object> trap_result_array;
1224  Handle<Object> args[] = {target};
1225  ASSIGN_RETURN_ON_EXCEPTION_VALUE(
1226      isolate_, trap_result_array,
1227      Execution::Call(isolate_, trap, handler, arraysize(args), args),
1228      Nothing<bool>());
1229  // 8. Let trapResult be ? CreateListFromArrayLike(trapResultArray,
1230  //    «String, Symbol»).
1231  Handle<FixedArray> trap_result;
1232  ASSIGN_RETURN_ON_EXCEPTION_VALUE(
1233      isolate_, trap_result,
1234      Object::CreateListFromArrayLike(isolate_, trap_result_array,
1235                                      ElementTypes::kStringAndSymbol),
1236      Nothing<bool>());
1237  // 9. If trapResult contains any duplicate entries, throw a TypeError
1238  // exception. Combine with step 18
1239  // 18. Let uncheckedResultKeys be a new List which is a copy of trapResult.
1240  Zone set_zone(isolate_->allocator(), ZONE_NAME);
1241
1242  const int kPresent = 1;
1243  const int kGone = 0;
1244  using ZoneHashMapImpl =
1245      base::TemplateHashMapImpl<Handle<Name>, int, NameComparator,
1246                                ZoneAllocationPolicy>;
1247  ZoneHashMapImpl unchecked_result_keys(
1248      ZoneHashMapImpl::kDefaultHashMapCapacity, NameComparator(isolate_),
1249      ZoneAllocationPolicy(&set_zone));
1250  int unchecked_result_keys_size = 0;
1251  for (int i = 0; i < trap_result->length(); ++i) {
1252    Handle<Name> key(Name::cast(trap_result->get(i)), isolate_);
1253    auto entry = unchecked_result_keys.LookupOrInsert(key, key->EnsureHash());
1254    if (entry->value != kPresent) {
1255      entry->value = kPresent;
1256      unchecked_result_keys_size++;
1257    } else {
1258      // found dupes, throw exception
1259      isolate_->Throw(*isolate_->factory()->NewTypeError(
1260          MessageTemplate::kProxyOwnKeysDuplicateEntries));
1261      return Nothing<bool>();
1262    }
1263  }
1264  // 10. Let extensibleTarget be ? IsExtensible(target).
1265  Maybe<bool> maybe_extensible = JSReceiver::IsExtensible(target);
1266  MAYBE_RETURN(maybe_extensible, Nothing<bool>());
1267  bool extensible_target = maybe_extensible.FromJust();
1268  // 11. Let targetKeys be ? target.[[OwnPropertyKeys]]().
1269  Handle<FixedArray> target_keys;
1270  ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate_, target_keys,
1271                                   JSReceiver::OwnPropertyKeys(target),
1272                                   Nothing<bool>());
1273  // 12, 13. (Assert)
1274  // 14. Let targetConfigurableKeys be an empty List.
1275  // To save memory, we're re-using target_keys and will modify it in-place.
1276  Handle<FixedArray> target_configurable_keys = target_keys;
1277  // 15. Let targetNonconfigurableKeys be an empty List.
1278  Handle<FixedArray> target_nonconfigurable_keys =
1279      isolate_->factory()->NewFixedArray(target_keys->length());
1280  int nonconfigurable_keys_length = 0;
1281  // 16. Repeat, for each element key of targetKeys:
1282  for (int i = 0; i < target_keys->length(); ++i) {
1283    // 16a. Let desc be ? target.[[GetOwnProperty]](key).
1284    PropertyDescriptor desc;
1285    Maybe<bool> found = JSReceiver::GetOwnPropertyDescriptor(
1286        isolate_, target, handle(target_keys->get(i), isolate_), &desc);
1287    MAYBE_RETURN(found, Nothing<bool>());
1288    // 16b. If desc is not undefined and desc.[[Configurable]] is false, then
1289    if (found.FromJust() && !desc.configurable()) {
1290      // 16b i. Append key as an element of targetNonconfigurableKeys.
1291      target_nonconfigurable_keys->set(nonconfigurable_keys_length,
1292                                       target_keys->get(i));
1293      nonconfigurable_keys_length++;
1294      // The key was moved, null it out in the original list.
1295      target_keys->set(i, Smi::zero());
1296    } else {
1297      // 16c. Else,
1298      // 16c i. Append key as an element of targetConfigurableKeys.
1299      // (No-op, just keep it in |target_keys|.)
1300    }
1301  }
1302  // 17. If extensibleTarget is true and targetNonconfigurableKeys is empty,
1303  //     then:
1304  if (extensible_target && nonconfigurable_keys_length == 0) {
1305    // 17a. Return trapResult.
1306    return AddKeysFromJSProxy(proxy, trap_result);
1307  }
1308  // 18. (Done in step 9)
1309  // 19. Repeat, for each key that is an element of targetNonconfigurableKeys:
1310  for (int i = 0; i < nonconfigurable_keys_length; ++i) {
1311    Object raw_key = target_nonconfigurable_keys->get(i);
1312    Handle<Name> key(Name::cast(raw_key), isolate_);
1313    // 19a. If key is not an element of uncheckedResultKeys, throw a
1314    //      TypeError exception.
1315    auto found = unchecked_result_keys.Lookup(key, key->hash());
1316    if (found == nullptr || found->value == kGone) {
1317      isolate_->Throw(*isolate_->factory()->NewTypeError(
1318          MessageTemplate::kProxyOwnKeysMissing, key));
1319      return Nothing<bool>();
1320    }
1321    // 19b. Remove key from uncheckedResultKeys.
1322    found->value = kGone;
1323    unchecked_result_keys_size--;
1324  }
1325  // 20. If extensibleTarget is true, return trapResult.
1326  if (extensible_target) {
1327    return AddKeysFromJSProxy(proxy, trap_result);
1328  }
1329  // 21. Repeat, for each key that is an element of targetConfigurableKeys:
1330  for (int i = 0; i < target_configurable_keys->length(); ++i) {
1331    Object raw_key = target_configurable_keys->get(i);
1332    if (raw_key.IsSmi()) continue;  // Zapped entry, was nonconfigurable.
1333    Handle<Name> key(Name::cast(raw_key), isolate_);
1334    // 21a. If key is not an element of uncheckedResultKeys, throw a
1335    //      TypeError exception.
1336    auto found = unchecked_result_keys.Lookup(key, key->hash());
1337    if (found == nullptr || found->value == kGone) {
1338      isolate_->Throw(*isolate_->factory()->NewTypeError(
1339          MessageTemplate::kProxyOwnKeysMissing, key));
1340      return Nothing<bool>();
1341    }
1342    // 21b. Remove key from uncheckedResultKeys.
1343    found->value = kGone;
1344    unchecked_result_keys_size--;
1345  }
1346  // 22. If uncheckedResultKeys is not empty, throw a TypeError exception.
1347  if (unchecked_result_keys_size != 0) {
1348    DCHECK_GT(unchecked_result_keys_size, 0);
1349    isolate_->Throw(*isolate_->factory()->NewTypeError(
1350        MessageTemplate::kProxyOwnKeysNonExtensible));
1351    return Nothing<bool>();
1352  }
1353  // 23. Return trapResult.
1354  return AddKeysFromJSProxy(proxy, trap_result);
1355}
1356
1357Maybe<bool> KeyAccumulator::CollectOwnJSProxyTargetKeys(
1358    Handle<JSProxy> proxy, Handle<JSReceiver> target) {
1359  // TODO(cbruni): avoid creating another KeyAccumulator
1360  Handle<FixedArray> keys;
1361  ASSIGN_RETURN_ON_EXCEPTION_VALUE(
1362      isolate_, keys,
1363      KeyAccumulator::GetKeys(
1364          target, KeyCollectionMode::kOwnOnly, ALL_PROPERTIES,
1365          GetKeysConversion::kConvertToString, is_for_in_, skip_indices_),
1366      Nothing<bool>());
1367  Maybe<bool> result = AddKeysFromJSProxy(proxy, keys);
1368  return result;
1369}
1370
1371#undef RETURN_NOTHING_IF_NOT_SUCCESSFUL
1372#undef RETURN_FAILURE_IF_NOT_SUCCESSFUL
1373}  // namespace internal
1374}  // namespace v8
1375