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