1 // Copyright 2013 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "src/keys.h"
6
7 #include "src/api-arguments-inl.h"
8 #include "src/elements-inl.h"
9 #include "src/handles-inl.h"
10 #include "src/heap/factory.h"
11 #include "src/identity-map.h"
12 #include "src/isolate-inl.h"
13 #include "src/objects-inl.h"
14 #include "src/objects/api-callbacks.h"
15 #include "src/objects/hash-table-inl.h"
16 #include "src/objects/module-inl.h"
17 #include "src/property-descriptor.h"
18 #include "src/prototype.h"
19
20 namespace v8 {
21 namespace internal {
22
~KeyAccumulator()23 KeyAccumulator::~KeyAccumulator() {
24 }
25
26 namespace {
27
ContainsOnlyValidKeys(Handle<FixedArray> array)28 static bool ContainsOnlyValidKeys(Handle<FixedArray> array) {
29 int len = array->length();
30 for (int i = 0; i < len; i++) {
31 Object* e = array->get(i);
32 if (!(e->IsName() || e->IsNumber())) return false;
33 }
34 return true;
35 }
36
37 } // namespace
38
39 // static
GetKeys(Handle<JSReceiver> object,KeyCollectionMode mode,PropertyFilter filter,GetKeysConversion keys_conversion,bool is_for_in,bool skip_indices)40 MaybeHandle<FixedArray> KeyAccumulator::GetKeys(
41 Handle<JSReceiver> object, KeyCollectionMode mode, PropertyFilter filter,
42 GetKeysConversion keys_conversion, bool is_for_in, bool skip_indices) {
43 Isolate* isolate = object->GetIsolate();
44 FastKeyAccumulator accumulator(isolate, object, mode, filter, is_for_in,
45 skip_indices);
46 return accumulator.GetKeys(keys_conversion);
47 }
48
GetKeys(GetKeysConversion convert)49 Handle<FixedArray> KeyAccumulator::GetKeys(GetKeysConversion convert) {
50 if (keys_.is_null()) {
51 return isolate_->factory()->empty_fixed_array();
52 }
53 if (mode_ == KeyCollectionMode::kOwnOnly &&
54 keys_->map() == ReadOnlyRoots(isolate_).fixed_array_map()) {
55 return Handle<FixedArray>::cast(keys_);
56 }
57 USE(ContainsOnlyValidKeys);
58 Handle<FixedArray> result =
59 OrderedHashSet::ConvertToKeysArray(isolate(), keys(), convert);
60 DCHECK(ContainsOnlyValidKeys(result));
61 return result;
62 }
63
keys()64 Handle<OrderedHashSet> KeyAccumulator::keys() {
65 return Handle<OrderedHashSet>::cast(keys_);
66 }
67
AddKey(Object * key,AddKeyConversion convert)68 void KeyAccumulator::AddKey(Object* key, AddKeyConversion convert) {
69 AddKey(handle(key, isolate_), convert);
70 }
71
AddKey(Handle<Object> key,AddKeyConversion convert)72 void KeyAccumulator::AddKey(Handle<Object> key, AddKeyConversion convert) {
73 if (key->IsSymbol()) {
74 if (filter_ & SKIP_SYMBOLS) return;
75 if (Handle<Symbol>::cast(key)->is_private()) return;
76 } else if (filter_ & SKIP_STRINGS) {
77 return;
78 }
79 if (IsShadowed(key)) return;
80 if (keys_.is_null()) {
81 keys_ = OrderedHashSet::Allocate(isolate_, 16);
82 }
83 uint32_t index;
84 if (convert == CONVERT_TO_ARRAY_INDEX && key->IsString() &&
85 Handle<String>::cast(key)->AsArrayIndex(&index)) {
86 key = isolate_->factory()->NewNumberFromUint(index);
87 }
88 Handle<OrderedHashSet> new_set = OrderedHashSet::Add(isolate(), keys(), key);
89 if (*new_set != *keys_) {
90 // The keys_ Set is converted directly to a FixedArray in GetKeys which can
91 // be left-trimmer. Hence the previous Set should not keep a pointer to the
92 // new one.
93 keys_->set(OrderedHashTableBase::kNextTableIndex, Smi::kZero);
94 keys_ = new_set;
95 }
96 }
97
AddKeys(Handle<FixedArray> array,AddKeyConversion convert)98 void KeyAccumulator::AddKeys(Handle<FixedArray> array,
99 AddKeyConversion convert) {
100 int add_length = array->length();
101 for (int i = 0; i < add_length; i++) {
102 Handle<Object> current(array->get(i), isolate_);
103 AddKey(current, convert);
104 }
105 }
106
AddKeys(Handle<JSObject> array_like,AddKeyConversion convert)107 void KeyAccumulator::AddKeys(Handle<JSObject> array_like,
108 AddKeyConversion convert) {
109 DCHECK(array_like->IsJSArray() || array_like->HasSloppyArgumentsElements());
110 ElementsAccessor* accessor = array_like->GetElementsAccessor();
111 accessor->AddElementsToKeyAccumulator(array_like, this, convert);
112 }
113
FilterProxyKeys(KeyAccumulator * accumulator,Handle<JSProxy> owner,Handle<FixedArray> keys,PropertyFilter filter)114 MaybeHandle<FixedArray> FilterProxyKeys(KeyAccumulator* accumulator,
115 Handle<JSProxy> owner,
116 Handle<FixedArray> keys,
117 PropertyFilter filter) {
118 if (filter == ALL_PROPERTIES) {
119 // Nothing to do.
120 return keys;
121 }
122 Isolate* isolate = accumulator->isolate();
123 int store_position = 0;
124 for (int i = 0; i < keys->length(); ++i) {
125 Handle<Name> key(Name::cast(keys->get(i)), isolate);
126 if (key->FilterKey(filter)) continue; // Skip this key.
127 if (filter & ONLY_ENUMERABLE) {
128 PropertyDescriptor desc;
129 Maybe<bool> found =
130 JSProxy::GetOwnPropertyDescriptor(isolate, owner, key, &desc);
131 MAYBE_RETURN(found, MaybeHandle<FixedArray>());
132 if (!found.FromJust()) continue;
133 if (!desc.enumerable()) {
134 accumulator->AddShadowingKey(key);
135 continue;
136 }
137 }
138 // Keep this key.
139 if (store_position != i) {
140 keys->set(store_position, *key);
141 }
142 store_position++;
143 }
144 return FixedArray::ShrinkOrEmpty(isolate, keys, store_position);
145 }
146
147 // Returns "nothing" in case of exception, "true" on success.
AddKeysFromJSProxy(Handle<JSProxy> proxy,Handle<FixedArray> keys)148 Maybe<bool> KeyAccumulator::AddKeysFromJSProxy(Handle<JSProxy> proxy,
149 Handle<FixedArray> keys) {
150 // Postpone the enumerable check for for-in to the ForInFilter step.
151 if (!is_for_in_) {
152 ASSIGN_RETURN_ON_EXCEPTION_VALUE(
153 isolate_, keys, FilterProxyKeys(this, proxy, keys, filter_),
154 Nothing<bool>());
155 if (mode_ == KeyCollectionMode::kOwnOnly) {
156 // If we collect only the keys from a JSProxy do not sort or deduplicate.
157 keys_ = keys;
158 return Just(true);
159 }
160 }
161 AddKeys(keys, is_for_in_ ? CONVERT_TO_ARRAY_INDEX : DO_NOT_CONVERT);
162 return Just(true);
163 }
164
CollectKeys(Handle<JSReceiver> receiver,Handle<JSReceiver> object)165 Maybe<bool> KeyAccumulator::CollectKeys(Handle<JSReceiver> receiver,
166 Handle<JSReceiver> object) {
167 // Proxies have no hidden prototype and we should not trigger the
168 // [[GetPrototypeOf]] trap on the last iteration when using
169 // AdvanceFollowingProxies.
170 if (mode_ == KeyCollectionMode::kOwnOnly && object->IsJSProxy()) {
171 MAYBE_RETURN(CollectOwnJSProxyKeys(receiver, Handle<JSProxy>::cast(object)),
172 Nothing<bool>());
173 return Just(true);
174 }
175
176 PrototypeIterator::WhereToEnd end = mode_ == KeyCollectionMode::kOwnOnly
177 ? PrototypeIterator::END_AT_NON_HIDDEN
178 : PrototypeIterator::END_AT_NULL;
179 for (PrototypeIterator iter(isolate_, object, kStartAtReceiver, end);
180 !iter.IsAtEnd();) {
181 // Start the shadow checks only after the first prototype has added
182 // shadowing keys.
183 if (HasShadowingKeys()) skip_shadow_check_ = false;
184 Handle<JSReceiver> current =
185 PrototypeIterator::GetCurrent<JSReceiver>(iter);
186 Maybe<bool> result = Just(false); // Dummy initialization.
187 if (current->IsJSProxy()) {
188 result = CollectOwnJSProxyKeys(receiver, Handle<JSProxy>::cast(current));
189 } else {
190 DCHECK(current->IsJSObject());
191 result = CollectOwnKeys(receiver, Handle<JSObject>::cast(current));
192 }
193 MAYBE_RETURN(result, Nothing<bool>());
194 if (!result.FromJust()) break; // |false| means "stop iterating".
195 // Iterate through proxies but ignore access checks for the ALL_CAN_READ
196 // case on API objects for OWN_ONLY keys handled in CollectOwnKeys.
197 if (!iter.AdvanceFollowingProxiesIgnoringAccessChecks()) {
198 return Nothing<bool>();
199 }
200 if (!last_non_empty_prototype_.is_null() &&
201 *last_non_empty_prototype_ == *current) {
202 break;
203 }
204 }
205 return Just(true);
206 }
207
HasShadowingKeys()208 bool KeyAccumulator::HasShadowingKeys() { return !shadowing_keys_.is_null(); }
209
IsShadowed(Handle<Object> key)210 bool KeyAccumulator::IsShadowed(Handle<Object> key) {
211 if (!HasShadowingKeys() || skip_shadow_check_) return false;
212 return shadowing_keys_->Has(isolate_, key);
213 }
214
AddShadowingKey(Object * key)215 void KeyAccumulator::AddShadowingKey(Object* key) {
216 if (mode_ == KeyCollectionMode::kOwnOnly) return;
217 AddShadowingKey(handle(key, isolate_));
218 }
AddShadowingKey(Handle<Object> key)219 void KeyAccumulator::AddShadowingKey(Handle<Object> key) {
220 if (mode_ == KeyCollectionMode::kOwnOnly) return;
221 if (shadowing_keys_.is_null()) {
222 shadowing_keys_ = ObjectHashSet::New(isolate_, 16);
223 }
224 shadowing_keys_ = ObjectHashSet::Add(isolate(), shadowing_keys_, key);
225 }
226
227 namespace {
228
TrySettingEmptyEnumCache(JSReceiver * object)229 void TrySettingEmptyEnumCache(JSReceiver* object) {
230 Map* map = object->map();
231 DCHECK_EQ(kInvalidEnumCacheSentinel, map->EnumLength());
232 if (!map->OnlyHasSimpleProperties()) return;
233 if (map->IsJSProxyMap()) return;
234 if (map->NumberOfEnumerableProperties() > 0) return;
235 DCHECK(object->IsJSObject());
236 map->SetEnumLength(0);
237 }
238
CheckAndInitalizeEmptyEnumCache(JSReceiver * object)239 bool CheckAndInitalizeEmptyEnumCache(JSReceiver* object) {
240 if (object->map()->EnumLength() == kInvalidEnumCacheSentinel) {
241 TrySettingEmptyEnumCache(object);
242 }
243 if (object->map()->EnumLength() != 0) return false;
244 DCHECK(object->IsJSObject());
245 return !JSObject::cast(object)->HasEnumerableElements();
246 }
247 } // namespace
248
Prepare()249 void FastKeyAccumulator::Prepare() {
250 DisallowHeapAllocation no_gc;
251 // Directly go for the fast path for OWN_ONLY keys.
252 if (mode_ == KeyCollectionMode::kOwnOnly) return;
253 // Fully walk the prototype chain and find the last prototype with keys.
254 is_receiver_simple_enum_ = false;
255 has_empty_prototype_ = true;
256 JSReceiver* last_prototype = nullptr;
257 for (PrototypeIterator iter(isolate_, *receiver_); !iter.IsAtEnd();
258 iter.Advance()) {
259 JSReceiver* current = iter.GetCurrent<JSReceiver>();
260 bool has_no_properties = CheckAndInitalizeEmptyEnumCache(current);
261 if (has_no_properties) continue;
262 last_prototype = current;
263 has_empty_prototype_ = false;
264 }
265 if (has_empty_prototype_) {
266 is_receiver_simple_enum_ =
267 receiver_->map()->EnumLength() != kInvalidEnumCacheSentinel &&
268 !JSObject::cast(*receiver_)->HasEnumerableElements();
269 } else if (last_prototype != nullptr) {
270 last_non_empty_prototype_ = handle(last_prototype, isolate_);
271 }
272 }
273
274 namespace {
275
ReduceFixedArrayTo(Isolate * isolate,Handle<FixedArray> array,int length)276 Handle<FixedArray> ReduceFixedArrayTo(Isolate* isolate,
277 Handle<FixedArray> array, int length) {
278 DCHECK_LE(length, array->length());
279 if (array->length() == length) return array;
280 return isolate->factory()->CopyFixedArrayUpTo(array, length);
281 }
282
283 // Initializes and directly returns the enume cache. Users of this function
284 // have to make sure to never directly leak the enum cache.
GetFastEnumPropertyKeys(Isolate * isolate,Handle<JSObject> object)285 Handle<FixedArray> GetFastEnumPropertyKeys(Isolate* isolate,
286 Handle<JSObject> object) {
287 Handle<Map> map(object->map(), isolate);
288 Handle<FixedArray> keys(map->instance_descriptors()->GetEnumCache()->keys(),
289 isolate);
290
291 // Check if the {map} has a valid enum length, which implies that it
292 // must have a valid enum cache as well.
293 int enum_length = map->EnumLength();
294 if (enum_length != kInvalidEnumCacheSentinel) {
295 DCHECK(map->OnlyHasSimpleProperties());
296 DCHECK_LE(enum_length, keys->length());
297 DCHECK_EQ(enum_length, map->NumberOfEnumerableProperties());
298 isolate->counters()->enum_cache_hits()->Increment();
299 return ReduceFixedArrayTo(isolate, keys, enum_length);
300 }
301
302 // Determine the actual number of enumerable properties of the {map}.
303 enum_length = map->NumberOfEnumerableProperties();
304
305 // Check if there's already a shared enum cache on the {map}s
306 // DescriptorArray with sufficient number of entries.
307 if (enum_length <= keys->length()) {
308 if (map->OnlyHasSimpleProperties()) map->SetEnumLength(enum_length);
309 isolate->counters()->enum_cache_hits()->Increment();
310 return ReduceFixedArrayTo(isolate, keys, enum_length);
311 }
312
313 Handle<DescriptorArray> descriptors =
314 Handle<DescriptorArray>(map->instance_descriptors(), isolate);
315 isolate->counters()->enum_cache_misses()->Increment();
316 int nod = map->NumberOfOwnDescriptors();
317
318 // Create the keys array.
319 int index = 0;
320 bool fields_only = true;
321 keys = isolate->factory()->NewFixedArray(enum_length);
322 for (int i = 0; i < nod; i++) {
323 DisallowHeapAllocation no_gc;
324 PropertyDetails details = descriptors->GetDetails(i);
325 if (details.IsDontEnum()) continue;
326 Object* key = descriptors->GetKey(i);
327 if (key->IsSymbol()) continue;
328 keys->set(index, key);
329 if (details.location() != kField) fields_only = false;
330 index++;
331 }
332 DCHECK_EQ(index, keys->length());
333
334 // Optionally also create the indices array.
335 Handle<FixedArray> indices = isolate->factory()->empty_fixed_array();
336 if (fields_only) {
337 indices = isolate->factory()->NewFixedArray(enum_length);
338 index = 0;
339 for (int i = 0; i < nod; i++) {
340 DisallowHeapAllocation no_gc;
341 PropertyDetails details = descriptors->GetDetails(i);
342 if (details.IsDontEnum()) continue;
343 Object* key = descriptors->GetKey(i);
344 if (key->IsSymbol()) continue;
345 DCHECK_EQ(kData, details.kind());
346 DCHECK_EQ(kField, details.location());
347 FieldIndex field_index = FieldIndex::ForDescriptor(*map, i);
348 indices->set(index, Smi::FromInt(field_index.GetLoadByFieldIndex()));
349 index++;
350 }
351 DCHECK_EQ(index, indices->length());
352 }
353
354 DescriptorArray::SetEnumCache(descriptors, isolate, keys, indices);
355 if (map->OnlyHasSimpleProperties()) map->SetEnumLength(enum_length);
356
357 return keys;
358 }
359
360 template <bool fast_properties>
GetOwnKeysWithElements(Isolate * isolate,Handle<JSObject> object,GetKeysConversion convert,bool skip_indices)361 MaybeHandle<FixedArray> GetOwnKeysWithElements(Isolate* isolate,
362 Handle<JSObject> object,
363 GetKeysConversion convert,
364 bool skip_indices) {
365 Handle<FixedArray> keys;
366 ElementsAccessor* accessor = object->GetElementsAccessor();
367 if (fast_properties) {
368 keys = GetFastEnumPropertyKeys(isolate, object);
369 } else {
370 // TODO(cbruni): preallocate big enough array to also hold elements.
371 keys = KeyAccumulator::GetOwnEnumPropertyKeys(isolate, object);
372 }
373
374 MaybeHandle<FixedArray> result;
375 if (skip_indices) {
376 result = keys;
377 } else {
378 result =
379 accessor->PrependElementIndices(object, keys, convert, ONLY_ENUMERABLE);
380 }
381
382 if (FLAG_trace_for_in_enumerate) {
383 PrintF("| strings=%d symbols=0 elements=%u || prototypes>=1 ||\n",
384 keys->length(), result.ToHandleChecked()->length() - keys->length());
385 }
386 return result;
387 }
388
389 } // namespace
390
GetKeys(GetKeysConversion keys_conversion)391 MaybeHandle<FixedArray> FastKeyAccumulator::GetKeys(
392 GetKeysConversion keys_conversion) {
393 if (filter_ == ENUMERABLE_STRINGS) {
394 Handle<FixedArray> keys;
395 if (GetKeysFast(keys_conversion).ToHandle(&keys)) {
396 return keys;
397 }
398 if (isolate_->has_pending_exception()) return MaybeHandle<FixedArray>();
399 }
400
401 return GetKeysSlow(keys_conversion);
402 }
403
GetKeysFast(GetKeysConversion keys_conversion)404 MaybeHandle<FixedArray> FastKeyAccumulator::GetKeysFast(
405 GetKeysConversion keys_conversion) {
406 bool own_only = has_empty_prototype_ || mode_ == KeyCollectionMode::kOwnOnly;
407 Map* map = receiver_->map();
408 if (!own_only || map->IsCustomElementsReceiverMap()) {
409 return MaybeHandle<FixedArray>();
410 }
411
412 // From this point on we are certain to only collect own keys.
413 DCHECK(receiver_->IsJSObject());
414 Handle<JSObject> object = Handle<JSObject>::cast(receiver_);
415
416 // Do not try to use the enum-cache for dict-mode objects.
417 if (map->is_dictionary_map()) {
418 return GetOwnKeysWithElements<false>(isolate_, object, keys_conversion,
419 skip_indices_);
420 }
421 int enum_length = receiver_->map()->EnumLength();
422 if (enum_length == kInvalidEnumCacheSentinel) {
423 Handle<FixedArray> keys;
424 // Try initializing the enum cache and return own properties.
425 if (GetOwnKeysWithUninitializedEnumCache().ToHandle(&keys)) {
426 if (FLAG_trace_for_in_enumerate) {
427 PrintF("| strings=%d symbols=0 elements=0 || prototypes>=1 ||\n",
428 keys->length());
429 }
430 is_receiver_simple_enum_ =
431 object->map()->EnumLength() != kInvalidEnumCacheSentinel;
432 return keys;
433 }
434 }
435 // The properties-only case failed because there were probably elements on the
436 // receiver.
437 return GetOwnKeysWithElements<true>(isolate_, object, keys_conversion,
438 skip_indices_);
439 }
440
441 MaybeHandle<FixedArray>
GetOwnKeysWithUninitializedEnumCache()442 FastKeyAccumulator::GetOwnKeysWithUninitializedEnumCache() {
443 Handle<JSObject> object = Handle<JSObject>::cast(receiver_);
444 // Uninitalized enum cache
445 Map* map = object->map();
446 if (object->elements()->length() != 0) {
447 // Assume that there are elements.
448 return MaybeHandle<FixedArray>();
449 }
450 int number_of_own_descriptors = map->NumberOfOwnDescriptors();
451 if (number_of_own_descriptors == 0) {
452 map->SetEnumLength(0);
453 return isolate_->factory()->empty_fixed_array();
454 }
455 // We have no elements but possibly enumerable property keys, hence we can
456 // directly initialize the enum cache.
457 Handle<FixedArray> keys = GetFastEnumPropertyKeys(isolate_, object);
458 if (is_for_in_) return keys;
459 // Do not leak the enum cache as it might end up as an elements backing store.
460 return isolate_->factory()->CopyFixedArray(keys);
461 }
462
GetKeysSlow(GetKeysConversion keys_conversion)463 MaybeHandle<FixedArray> FastKeyAccumulator::GetKeysSlow(
464 GetKeysConversion keys_conversion) {
465 KeyAccumulator accumulator(isolate_, mode_, filter_);
466 accumulator.set_is_for_in(is_for_in_);
467 accumulator.set_skip_indices(skip_indices_);
468 accumulator.set_last_non_empty_prototype(last_non_empty_prototype_);
469
470 MAYBE_RETURN(accumulator.CollectKeys(receiver_, receiver_),
471 MaybeHandle<FixedArray>());
472 return accumulator.GetKeys(keys_conversion);
473 }
474
475 namespace {
476
477 enum IndexedOrNamed { kIndexed, kNamed };
478
FilterForEnumerableProperties(Handle<JSReceiver> receiver,Handle<JSObject> object,Handle<InterceptorInfo> interceptor,KeyAccumulator * accumulator,Handle<JSObject> result,IndexedOrNamed type)479 void FilterForEnumerableProperties(Handle<JSReceiver> receiver,
480 Handle<JSObject> object,
481 Handle<InterceptorInfo> interceptor,
482 KeyAccumulator* accumulator,
483 Handle<JSObject> result,
484 IndexedOrNamed type) {
485 DCHECK(result->IsJSArray() || result->HasSloppyArgumentsElements());
486 ElementsAccessor* accessor = result->GetElementsAccessor();
487
488 uint32_t length = accessor->GetCapacity(*result, result->elements());
489 for (uint32_t i = 0; i < length; i++) {
490 if (!accessor->HasEntry(*result, i)) continue;
491
492 // args are invalid after args.Call(), create a new one in every iteration.
493 PropertyCallbackArguments args(accumulator->isolate(), interceptor->data(),
494 *receiver, *object, kDontThrow);
495
496 Handle<Object> element = accessor->Get(result, i);
497 Handle<Object> attributes;
498 if (type == kIndexed) {
499 uint32_t number;
500 CHECK(element->ToUint32(&number));
501 attributes = args.CallIndexedQuery(interceptor, number);
502 } else {
503 CHECK(element->IsName());
504 attributes =
505 args.CallNamedQuery(interceptor, Handle<Name>::cast(element));
506 }
507
508 if (!attributes.is_null()) {
509 int32_t value;
510 CHECK(attributes->ToInt32(&value));
511 if ((value & DONT_ENUM) == 0) {
512 accumulator->AddKey(element, DO_NOT_CONVERT);
513 }
514 }
515 }
516 }
517
518 // Returns |true| on success, |nothing| on exception.
CollectInterceptorKeysInternal(Handle<JSReceiver> receiver,Handle<JSObject> object,Handle<InterceptorInfo> interceptor,KeyAccumulator * accumulator,IndexedOrNamed type)519 Maybe<bool> CollectInterceptorKeysInternal(Handle<JSReceiver> receiver,
520 Handle<JSObject> object,
521 Handle<InterceptorInfo> interceptor,
522 KeyAccumulator* accumulator,
523 IndexedOrNamed type) {
524 Isolate* isolate = accumulator->isolate();
525 PropertyCallbackArguments enum_args(isolate, interceptor->data(), *receiver,
526 *object, kDontThrow);
527
528 Handle<JSObject> result;
529 if (!interceptor->enumerator()->IsUndefined(isolate)) {
530 if (type == kIndexed) {
531 result = enum_args.CallIndexedEnumerator(interceptor);
532 } else {
533 DCHECK_EQ(type, kNamed);
534 result = enum_args.CallNamedEnumerator(interceptor);
535 }
536 }
537 RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, Nothing<bool>());
538 if (result.is_null()) return Just(true);
539
540 if ((accumulator->filter() & ONLY_ENUMERABLE) &&
541 !interceptor->query()->IsUndefined(isolate)) {
542 FilterForEnumerableProperties(receiver, object, interceptor, accumulator,
543 result, type);
544 } else {
545 accumulator->AddKeys(
546 result, type == kIndexed ? CONVERT_TO_ARRAY_INDEX : DO_NOT_CONVERT);
547 }
548 return Just(true);
549 }
550
CollectInterceptorKeys(Handle<JSReceiver> receiver,Handle<JSObject> object,KeyAccumulator * accumulator,IndexedOrNamed type)551 Maybe<bool> CollectInterceptorKeys(Handle<JSReceiver> receiver,
552 Handle<JSObject> object,
553 KeyAccumulator* accumulator,
554 IndexedOrNamed type) {
555 Isolate* isolate = accumulator->isolate();
556 if (type == kIndexed) {
557 if (!object->HasIndexedInterceptor()) return Just(true);
558 } else {
559 if (!object->HasNamedInterceptor()) return Just(true);
560 }
561 Handle<InterceptorInfo> interceptor(type == kIndexed
562 ? object->GetIndexedInterceptor()
563 : object->GetNamedInterceptor(),
564 isolate);
565 if ((accumulator->filter() & ONLY_ALL_CAN_READ) &&
566 !interceptor->all_can_read()) {
567 return Just(true);
568 }
569 return CollectInterceptorKeysInternal(receiver, object, interceptor,
570 accumulator, type);
571 }
572
573 } // namespace
574
CollectOwnElementIndices(Handle<JSReceiver> receiver,Handle<JSObject> object)575 Maybe<bool> KeyAccumulator::CollectOwnElementIndices(
576 Handle<JSReceiver> receiver, Handle<JSObject> object) {
577 if (filter_ & SKIP_STRINGS || skip_indices_) return Just(true);
578
579 ElementsAccessor* accessor = object->GetElementsAccessor();
580 accessor->CollectElementIndices(object, this);
581
582 return CollectInterceptorKeys(receiver, object, this, kIndexed);
583 }
584
585 namespace {
586
587 template <bool skip_symbols>
CollectOwnPropertyNamesInternal(Handle<JSObject> object,KeyAccumulator * keys,Handle<DescriptorArray> descs,int start_index,int limit)588 int CollectOwnPropertyNamesInternal(Handle<JSObject> object,
589 KeyAccumulator* keys,
590 Handle<DescriptorArray> descs,
591 int start_index, int limit) {
592 int first_skipped = -1;
593 PropertyFilter filter = keys->filter();
594 KeyCollectionMode mode = keys->mode();
595 for (int i = start_index; i < limit; i++) {
596 bool is_shadowing_key = false;
597 PropertyDetails details = descs->GetDetails(i);
598
599 if ((details.attributes() & filter) != 0) {
600 if (mode == KeyCollectionMode::kIncludePrototypes) {
601 is_shadowing_key = true;
602 } else {
603 continue;
604 }
605 }
606
607 if (filter & ONLY_ALL_CAN_READ) {
608 if (details.kind() != kAccessor) continue;
609 Object* accessors = descs->GetStrongValue(i);
610 if (!accessors->IsAccessorInfo()) continue;
611 if (!AccessorInfo::cast(accessors)->all_can_read()) continue;
612 }
613
614 Name* key = descs->GetKey(i);
615 if (skip_symbols == key->IsSymbol()) {
616 if (first_skipped == -1) first_skipped = i;
617 continue;
618 }
619 if (key->FilterKey(keys->filter())) continue;
620
621 if (is_shadowing_key) {
622 keys->AddShadowingKey(key);
623 } else {
624 keys->AddKey(key, DO_NOT_CONVERT);
625 }
626 }
627 return first_skipped;
628 }
629
630 template <class T>
GetOwnEnumPropertyDictionaryKeys(Isolate * isolate,KeyCollectionMode mode,KeyAccumulator * accumulator,Handle<JSObject> object,T * raw_dictionary)631 Handle<FixedArray> GetOwnEnumPropertyDictionaryKeys(Isolate* isolate,
632 KeyCollectionMode mode,
633 KeyAccumulator* accumulator,
634 Handle<JSObject> object,
635 T* raw_dictionary) {
636 Handle<T> dictionary(raw_dictionary, isolate);
637 int length = dictionary->NumberOfEnumerableProperties();
638 if (length == 0) {
639 return isolate->factory()->empty_fixed_array();
640 }
641 Handle<FixedArray> storage = isolate->factory()->NewFixedArray(length);
642 T::CopyEnumKeysTo(isolate, dictionary, storage, mode, accumulator);
643 return storage;
644 }
645 } // namespace
646
CollectOwnPropertyNames(Handle<JSReceiver> receiver,Handle<JSObject> object)647 Maybe<bool> KeyAccumulator::CollectOwnPropertyNames(Handle<JSReceiver> receiver,
648 Handle<JSObject> object) {
649 if (filter_ == ENUMERABLE_STRINGS) {
650 Handle<FixedArray> enum_keys;
651 if (object->HasFastProperties()) {
652 enum_keys = KeyAccumulator::GetOwnEnumPropertyKeys(isolate_, object);
653 // If the number of properties equals the length of enumerable properties
654 // we do not have to filter out non-enumerable ones
655 Map* map = object->map();
656 int nof_descriptors = map->NumberOfOwnDescriptors();
657 if (enum_keys->length() != nof_descriptors) {
658 Handle<DescriptorArray> descs =
659 Handle<DescriptorArray>(map->instance_descriptors(), isolate_);
660 for (int i = 0; i < nof_descriptors; i++) {
661 PropertyDetails details = descs->GetDetails(i);
662 if (!details.IsDontEnum()) continue;
663 Object* key = descs->GetKey(i);
664 this->AddShadowingKey(key);
665 }
666 }
667 } else if (object->IsJSGlobalObject()) {
668 enum_keys = GetOwnEnumPropertyDictionaryKeys(
669 isolate_, mode_, this, object,
670 JSGlobalObject::cast(*object)->global_dictionary());
671 } else {
672 enum_keys = GetOwnEnumPropertyDictionaryKeys(
673 isolate_, mode_, this, object, object->property_dictionary());
674 }
675 if (object->IsJSModuleNamespace()) {
676 // Simulate [[GetOwnProperty]] for establishing enumerability, which
677 // throws for uninitialized exports.
678 for (int i = 0, n = enum_keys->length(); i < n; ++i) {
679 Handle<String> key(String::cast(enum_keys->get(i)), isolate_);
680 if (Handle<JSModuleNamespace>::cast(object)
681 ->GetExport(isolate(), key)
682 .is_null()) {
683 return Nothing<bool>();
684 }
685 }
686 }
687 AddKeys(enum_keys, DO_NOT_CONVERT);
688 } else {
689 if (object->HasFastProperties()) {
690 int limit = object->map()->NumberOfOwnDescriptors();
691 Handle<DescriptorArray> descs(object->map()->instance_descriptors(),
692 isolate_);
693 // First collect the strings,
694 int first_symbol =
695 CollectOwnPropertyNamesInternal<true>(object, this, descs, 0, limit);
696 // then the symbols.
697 if (first_symbol != -1) {
698 CollectOwnPropertyNamesInternal<false>(object, this, descs,
699 first_symbol, limit);
700 }
701 } else if (object->IsJSGlobalObject()) {
702 GlobalDictionary::CollectKeysTo(
703 handle(JSGlobalObject::cast(*object)->global_dictionary(), isolate_),
704 this);
705 } else {
706 NameDictionary::CollectKeysTo(
707 handle(object->property_dictionary(), isolate_), this);
708 }
709 }
710 // Add the property keys from the interceptor.
711 return CollectInterceptorKeys(receiver, object, this, kNamed);
712 }
713
CollectAccessCheckInterceptorKeys(Handle<AccessCheckInfo> access_check_info,Handle<JSReceiver> receiver,Handle<JSObject> object)714 Maybe<bool> KeyAccumulator::CollectAccessCheckInterceptorKeys(
715 Handle<AccessCheckInfo> access_check_info, Handle<JSReceiver> receiver,
716 Handle<JSObject> object) {
717 if (!skip_indices_) {
718 MAYBE_RETURN((CollectInterceptorKeysInternal(
719 receiver, object,
720 handle(InterceptorInfo::cast(
721 access_check_info->indexed_interceptor()),
722 isolate_),
723 this, kIndexed)),
724 Nothing<bool>());
725 }
726 MAYBE_RETURN(
727 (CollectInterceptorKeysInternal(
728 receiver, object,
729 handle(InterceptorInfo::cast(access_check_info->named_interceptor()),
730 isolate_),
731 this, kNamed)),
732 Nothing<bool>());
733 return Just(true);
734 }
735
736 // Returns |true| on success, |false| if prototype walking should be stopped,
737 // |nothing| if an exception was thrown.
CollectOwnKeys(Handle<JSReceiver> receiver,Handle<JSObject> object)738 Maybe<bool> KeyAccumulator::CollectOwnKeys(Handle<JSReceiver> receiver,
739 Handle<JSObject> object) {
740 // Check access rights if required.
741 if (object->IsAccessCheckNeeded() &&
742 !isolate_->MayAccess(handle(isolate_->context(), isolate_), object)) {
743 // The cross-origin spec says that [[Enumerate]] shall return an empty
744 // iterator when it doesn't have access...
745 if (mode_ == KeyCollectionMode::kIncludePrototypes) {
746 return Just(false);
747 }
748 // ...whereas [[OwnPropertyKeys]] shall return whitelisted properties.
749 DCHECK_EQ(KeyCollectionMode::kOwnOnly, mode_);
750 Handle<AccessCheckInfo> access_check_info;
751 {
752 DisallowHeapAllocation no_gc;
753 AccessCheckInfo* maybe_info = AccessCheckInfo::Get(isolate_, object);
754 if (maybe_info) access_check_info = handle(maybe_info, isolate_);
755 }
756 // We always have both kinds of interceptors or none.
757 if (!access_check_info.is_null() &&
758 access_check_info->named_interceptor()) {
759 MAYBE_RETURN(CollectAccessCheckInterceptorKeys(access_check_info,
760 receiver, object),
761 Nothing<bool>());
762 return Just(false);
763 }
764 filter_ = static_cast<PropertyFilter>(filter_ | ONLY_ALL_CAN_READ);
765 }
766 MAYBE_RETURN(CollectOwnElementIndices(receiver, object), Nothing<bool>());
767 MAYBE_RETURN(CollectOwnPropertyNames(receiver, object), Nothing<bool>());
768 return Just(true);
769 }
770
771 // static
GetOwnEnumPropertyKeys(Isolate * isolate,Handle<JSObject> object)772 Handle<FixedArray> KeyAccumulator::GetOwnEnumPropertyKeys(
773 Isolate* isolate, Handle<JSObject> object) {
774 if (object->HasFastProperties()) {
775 return GetFastEnumPropertyKeys(isolate, object);
776 } else if (object->IsJSGlobalObject()) {
777 return GetOwnEnumPropertyDictionaryKeys(
778 isolate, KeyCollectionMode::kOwnOnly, nullptr, object,
779 JSGlobalObject::cast(*object)->global_dictionary());
780 } else {
781 return GetOwnEnumPropertyDictionaryKeys(
782 isolate, KeyCollectionMode::kOwnOnly, nullptr, object,
783 object->property_dictionary());
784 }
785 }
786
787 namespace {
788
789 class NameComparator {
790 public:
NameComparator(Isolate * isolate)791 explicit NameComparator(Isolate* isolate) : isolate_(isolate) {}
792
operator ()(uint32_t hash1,uint32_t hash2,const Handle<Name> & key1,const Handle<Name> & key2) const793 bool operator()(uint32_t hash1, uint32_t hash2, const Handle<Name>& key1,
794 const Handle<Name>& key2) const {
795 return Name::Equals(isolate_, key1, key2);
796 }
797
798 private:
799 Isolate* isolate_;
800 };
801
802 } // namespace
803
804 // ES6 9.5.12
805 // Returns |true| on success, |nothing| in case of exception.
CollectOwnJSProxyKeys(Handle<JSReceiver> receiver,Handle<JSProxy> proxy)806 Maybe<bool> KeyAccumulator::CollectOwnJSProxyKeys(Handle<JSReceiver> receiver,
807 Handle<JSProxy> proxy) {
808 STACK_CHECK(isolate_, Nothing<bool>());
809 // 1. Let handler be the value of the [[ProxyHandler]] internal slot of O.
810 Handle<Object> handler(proxy->handler(), isolate_);
811 // 2. If handler is null, throw a TypeError exception.
812 // 3. Assert: Type(handler) is Object.
813 if (proxy->IsRevoked()) {
814 isolate_->Throw(*isolate_->factory()->NewTypeError(
815 MessageTemplate::kProxyRevoked, isolate_->factory()->ownKeys_string()));
816 return Nothing<bool>();
817 }
818 // 4. Let target be the value of the [[ProxyTarget]] internal slot of O.
819 Handle<JSReceiver> target(JSReceiver::cast(proxy->target()), isolate_);
820 // 5. Let trap be ? GetMethod(handler, "ownKeys").
821 Handle<Object> trap;
822 ASSIGN_RETURN_ON_EXCEPTION_VALUE(
823 isolate_, trap, Object::GetMethod(Handle<JSReceiver>::cast(handler),
824 isolate_->factory()->ownKeys_string()),
825 Nothing<bool>());
826 // 6. If trap is undefined, then
827 if (trap->IsUndefined(isolate_)) {
828 // 6a. Return target.[[OwnPropertyKeys]]().
829 return CollectOwnJSProxyTargetKeys(proxy, target);
830 }
831 // 7. Let trapResultArray be Call(trap, handler, «target»).
832 Handle<Object> trap_result_array;
833 Handle<Object> args[] = {target};
834 ASSIGN_RETURN_ON_EXCEPTION_VALUE(
835 isolate_, trap_result_array,
836 Execution::Call(isolate_, trap, handler, arraysize(args), args),
837 Nothing<bool>());
838 // 8. Let trapResult be ? CreateListFromArrayLike(trapResultArray,
839 // «String, Symbol»).
840 Handle<FixedArray> trap_result;
841 ASSIGN_RETURN_ON_EXCEPTION_VALUE(
842 isolate_, trap_result,
843 Object::CreateListFromArrayLike(isolate_, trap_result_array,
844 ElementTypes::kStringAndSymbol),
845 Nothing<bool>());
846 // 9. Let extensibleTarget be ? IsExtensible(target).
847 Maybe<bool> maybe_extensible = JSReceiver::IsExtensible(target);
848 MAYBE_RETURN(maybe_extensible, Nothing<bool>());
849 bool extensible_target = maybe_extensible.FromJust();
850 // 10. Let targetKeys be ? target.[[OwnPropertyKeys]]().
851 Handle<FixedArray> target_keys;
852 ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate_, target_keys,
853 JSReceiver::OwnPropertyKeys(target),
854 Nothing<bool>());
855 // 11. (Assert)
856 // 12. Let targetConfigurableKeys be an empty List.
857 // To save memory, we're re-using target_keys and will modify it in-place.
858 Handle<FixedArray> target_configurable_keys = target_keys;
859 // 13. Let targetNonconfigurableKeys be an empty List.
860 Handle<FixedArray> target_nonconfigurable_keys =
861 isolate_->factory()->NewFixedArray(target_keys->length());
862 int nonconfigurable_keys_length = 0;
863 // 14. Repeat, for each element key of targetKeys:
864 for (int i = 0; i < target_keys->length(); ++i) {
865 // 14a. Let desc be ? target.[[GetOwnProperty]](key).
866 PropertyDescriptor desc;
867 Maybe<bool> found = JSReceiver::GetOwnPropertyDescriptor(
868 isolate_, target, handle(target_keys->get(i), isolate_), &desc);
869 MAYBE_RETURN(found, Nothing<bool>());
870 // 14b. If desc is not undefined and desc.[[Configurable]] is false, then
871 if (found.FromJust() && !desc.configurable()) {
872 // 14b i. Append key as an element of targetNonconfigurableKeys.
873 target_nonconfigurable_keys->set(nonconfigurable_keys_length,
874 target_keys->get(i));
875 nonconfigurable_keys_length++;
876 // The key was moved, null it out in the original list.
877 target_keys->set(i, Smi::kZero);
878 } else {
879 // 14c. Else,
880 // 14c i. Append key as an element of targetConfigurableKeys.
881 // (No-op, just keep it in |target_keys|.)
882 }
883 }
884 // 15. If extensibleTarget is true and targetNonconfigurableKeys is empty,
885 // then:
886 if (extensible_target && nonconfigurable_keys_length == 0) {
887 // 15a. Return trapResult.
888 return AddKeysFromJSProxy(proxy, trap_result);
889 }
890 // 16. Let uncheckedResultKeys be a new List which is a copy of trapResult.
891 Zone set_zone(isolate_->allocator(), ZONE_NAME);
892 ZoneAllocationPolicy alloc(&set_zone);
893 const int kPresent = 1;
894 const int kGone = 0;
895 base::TemplateHashMapImpl<Handle<Name>, int, NameComparator,
896 ZoneAllocationPolicy>
897 unchecked_result_keys(ZoneHashMap::kDefaultHashMapCapacity,
898 NameComparator(isolate_), alloc);
899 int unchecked_result_keys_size = 0;
900 for (int i = 0; i < trap_result->length(); ++i) {
901 Handle<Name> key(Name::cast(trap_result->get(i)), isolate_);
902 auto entry = unchecked_result_keys.LookupOrInsert(key, key->Hash(), alloc);
903 if (entry->value != kPresent) {
904 entry->value = kPresent;
905 unchecked_result_keys_size++;
906 }
907 }
908 // 17. Repeat, for each key that is an element of targetNonconfigurableKeys:
909 for (int i = 0; i < nonconfigurable_keys_length; ++i) {
910 Object* raw_key = target_nonconfigurable_keys->get(i);
911 Handle<Name> key(Name::cast(raw_key), isolate_);
912 // 17a. If key is not an element of uncheckedResultKeys, throw a
913 // TypeError exception.
914 auto found = unchecked_result_keys.Lookup(key, key->Hash());
915 if (found == nullptr || found->value == kGone) {
916 isolate_->Throw(*isolate_->factory()->NewTypeError(
917 MessageTemplate::kProxyOwnKeysMissing, key));
918 return Nothing<bool>();
919 }
920 // 17b. Remove key from uncheckedResultKeys.
921 found->value = kGone;
922 unchecked_result_keys_size--;
923 }
924 // 18. If extensibleTarget is true, return trapResult.
925 if (extensible_target) {
926 return AddKeysFromJSProxy(proxy, trap_result);
927 }
928 // 19. Repeat, for each key that is an element of targetConfigurableKeys:
929 for (int i = 0; i < target_configurable_keys->length(); ++i) {
930 Object* raw_key = target_configurable_keys->get(i);
931 if (raw_key->IsSmi()) continue; // Zapped entry, was nonconfigurable.
932 Handle<Name> key(Name::cast(raw_key), isolate_);
933 // 19a. If key is not an element of uncheckedResultKeys, throw a
934 // TypeError exception.
935 auto found = unchecked_result_keys.Lookup(key, key->Hash());
936 if (found == nullptr || found->value == kGone) {
937 isolate_->Throw(*isolate_->factory()->NewTypeError(
938 MessageTemplate::kProxyOwnKeysMissing, key));
939 return Nothing<bool>();
940 }
941 // 19b. Remove key from uncheckedResultKeys.
942 found->value = kGone;
943 unchecked_result_keys_size--;
944 }
945 // 20. If uncheckedResultKeys is not empty, throw a TypeError exception.
946 if (unchecked_result_keys_size != 0) {
947 DCHECK_GT(unchecked_result_keys_size, 0);
948 isolate_->Throw(*isolate_->factory()->NewTypeError(
949 MessageTemplate::kProxyOwnKeysNonExtensible));
950 return Nothing<bool>();
951 }
952 // 21. Return trapResult.
953 return AddKeysFromJSProxy(proxy, trap_result);
954 }
955
CollectOwnJSProxyTargetKeys(Handle<JSProxy> proxy,Handle<JSReceiver> target)956 Maybe<bool> KeyAccumulator::CollectOwnJSProxyTargetKeys(
957 Handle<JSProxy> proxy, Handle<JSReceiver> target) {
958 // TODO(cbruni): avoid creating another KeyAccumulator
959 Handle<FixedArray> keys;
960 ASSIGN_RETURN_ON_EXCEPTION_VALUE(
961 isolate_, keys,
962 KeyAccumulator::GetKeys(
963 target, KeyCollectionMode::kOwnOnly, ALL_PROPERTIES,
964 GetKeysConversion::kConvertToString, is_for_in_, skip_indices_),
965 Nothing<bool>());
966 Maybe<bool> result = AddKeysFromJSProxy(proxy, keys);
967 return result;
968 }
969
970 } // namespace internal
971 } // namespace v8
972