• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
25 namespace v8 {
26 namespace 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 
39 namespace {
40 
ContainsOnlyValidKeys(Handle<FixedArray> array)41 static 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 
AddKey(Object key,Handle<FixedArray> combined_keys,Handle<DescriptorArray> descs,int nof_descriptors,int target)50 static 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 
CombineKeys(Isolate * isolate,Handle<FixedArray> own_keys,Handle<FixedArray> prototype_chain_keys,Handle<JSReceiver> receiver,bool may_have_elements)60 static 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
GetKeys(Handle<JSReceiver> object,KeyCollectionMode mode,PropertyFilter filter,GetKeysConversion keys_conversion,bool is_for_in,bool skip_indices)90 MaybeHandle<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 
GetKeys(GetKeysConversion convert)99 Handle<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 
keys()118 Handle<OrderedHashSet> KeyAccumulator::keys() {
119   return Handle<OrderedHashSet>::cast(keys_);
120 }
121 
AddKey(Object key,AddKeyConversion convert)122 ExceptionStatus KeyAccumulator::AddKey(Object key, AddKeyConversion convert) {
123   return AddKey(handle(key, isolate_), convert);
124 }
125 
AddKey(Handle<Object> key,AddKeyConversion convert)126 ExceptionStatus 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 
AddKeys(Handle<FixedArray> array,AddKeyConversion convert)165 ExceptionStatus 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 
AddKeys(Handle<JSObject> array_like,AddKeyConversion convert)175 ExceptionStatus 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 
FilterProxyKeys(KeyAccumulator * accumulator,Handle<JSProxy> owner,Handle<FixedArray> keys,PropertyFilter filter,bool skip_indices)182 MaybeHandle<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.
AddKeysFromJSProxy(Handle<JSProxy> proxy,Handle<FixedArray> keys)221 Maybe<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 
CollectKeys(Handle<JSReceiver> receiver,Handle<JSReceiver> object)238 Maybe<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 
HasShadowingKeys()281 bool KeyAccumulator::HasShadowingKeys() { return !shadowing_keys_.is_null(); }
282 
IsShadowed(Handle<Object> key)283 bool KeyAccumulator::IsShadowed(Handle<Object> key) {
284   if (!HasShadowingKeys() || skip_shadow_check_) return false;
285   return shadowing_keys_->Has(isolate_, key);
286 }
287 
AddShadowingKey(Object key,AllowGarbageCollection * allow_gc)288 void KeyAccumulator::AddShadowingKey(Object key,
289                                      AllowGarbageCollection* allow_gc) {
290   if (mode_ == KeyCollectionMode::kOwnOnly) return;
291   AddShadowingKey(handle(key, isolate_));
292 }
AddShadowingKey(Handle<Object> key)293 void 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 
301 namespace {
302 
TrySettingEmptyEnumCache(JSReceiver object)303 void 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 
CheckAndInitalizeEmptyEnumCache(JSReceiver object)313 bool 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 
Prepare()323 void 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 
360 namespace {
361 
ReduceFixedArrayTo(Isolate * isolate,Handle<FixedArray> array,int length)362 Handle<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.
GetFastEnumPropertyKeys(Isolate * isolate,Handle<JSObject> object)371 Handle<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 
446 template <bool fast_properties>
GetOwnKeysWithElements(Isolate * isolate,Handle<JSObject> object,GetKeysConversion convert,bool skip_indices)447 MaybeHandle<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 
GetKeys(GetKeysConversion keys_conversion)477 MaybeHandle<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 
GetKeysFast(GetKeysConversion keys_conversion)498 MaybeHandle<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 
535 MaybeHandle<FixedArray>
GetOwnKeysWithUninitializedEnumCache()536 FastKeyAccumulator::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 
GetKeysSlow(GetKeysConversion keys_conversion)559 MaybeHandle<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 
GetKeysWithPrototypeInfoCache(GetKeysConversion keys_conversion)574 MaybeHandle<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 
MayHaveElements(JSReceiver receiver)623 bool 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 
TryPrototypeInfoCache(Handle<JSReceiver> receiver)631 bool 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 
656 V8_WARN_UNUSED_RESULT ExceptionStatus
FilterForEnumerableProperties(Handle<JSReceiver> receiver,Handle<JSObject> object,Handle<InterceptorInfo> interceptor,Handle<JSObject> result,IndexedOrNamed type)657 KeyAccumulator::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.
CollectInterceptorKeysInternal(Handle<JSReceiver> receiver,Handle<JSObject> object,Handle<InterceptorInfo> interceptor,IndexedOrNamed type)696 Maybe<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 
CollectInterceptorKeys(Handle<JSReceiver> receiver,Handle<JSObject> object,IndexedOrNamed type)725 Maybe<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 
CollectOwnElementIndices(Handle<JSReceiver> receiver,Handle<JSObject> object)743 Maybe<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 
753 namespace {
754 
755 template <bool skip_symbols>
CollectOwnPropertyNamesInternal(Handle<JSObject> object,KeyAccumulator * keys,Handle<DescriptorArray> descs,int start_index,int limit)756 base::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.
803 template <typename Dictionary>
CommonCopyEnumKeysTo(Isolate * isolate,Handle<Dictionary> dictionary,Handle<FixedArray> storage,KeyCollectionMode mode,KeyAccumulator * accumulator)804 void 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.
851 template <typename Dictionary>
CopyEnumKeysTo(Isolate * isolate,Handle<Dictionary> dictionary,Handle<FixedArray> storage,KeyCollectionMode mode,KeyAccumulator * accumulator)852 void 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 
876 template <>
CopyEnumKeysTo(Isolate * isolate,Handle<SwissNameDictionary> dictionary,Handle<FixedArray> storage,KeyCollectionMode mode,KeyAccumulator * accumulator)877 void 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 
889 template <class T>
GetOwnEnumPropertyDictionaryKeys(Isolate * isolate,KeyCollectionMode mode,KeyAccumulator * accumulator,Handle<JSObject> object,T raw_dictionary)890 Handle<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.
907 template <typename Dictionary>
CollectKeysFromDictionary(Handle<Dictionary> dictionary,KeyAccumulator * keys)908 ExceptionStatus 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 
CollectOwnPropertyNames(Handle<JSReceiver> receiver,Handle<JSObject> object)980 Maybe<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 
CollectPrivateNames(Handle<JSReceiver> receiver,Handle<JSObject> object)1057 ExceptionStatus 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 
CollectAccessCheckInterceptorKeys(Handle<AccessCheckInfo> access_check_info,Handle<JSReceiver> receiver,Handle<JSObject> object)1080 Maybe<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.
CollectOwnKeys(Handle<JSReceiver> receiver,Handle<JSObject> object)1104 Maybe<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
GetOwnEnumPropertyKeys(Isolate * isolate,Handle<JSObject> object)1147 Handle<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 
1166 namespace {
1167 
1168 class NameComparator {
1169  public:
NameComparator(Isolate * isolate)1170   explicit NameComparator(Isolate* isolate) : isolate_(isolate) {}
1171 
operator ()(uint32_t hash1,uint32_t hash2,const Handle<Name> & key1,const Handle<Name> & key2) const1172   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.
CollectOwnJSProxyKeys(Handle<JSReceiver> receiver,Handle<JSProxy> proxy)1185 Maybe<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 
CollectOwnJSProxyTargetKeys(Handle<JSProxy> proxy,Handle<JSReceiver> target)1357 Maybe<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