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