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