• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2013 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "src/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 
CheckAndInitalizeEmptyEnumCache(JSReceiver * object)230 bool CheckAndInitalizeEmptyEnumCache(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 = CheckAndInitalizeEmptyEnumCache(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 
274 // Initializes and directly returns the enume cache. Users of this function
275 // have to make sure to never directly leak the enum cache.
GetFastEnumPropertyKeys(Isolate * isolate,Handle<JSObject> object)276 Handle<FixedArray> GetFastEnumPropertyKeys(Isolate* isolate,
277                                            Handle<JSObject> object) {
278   Handle<Map> map(object->map());
279   bool cache_enum_length = map->OnlyHasSimpleProperties();
280 
281   Handle<DescriptorArray> descs =
282       Handle<DescriptorArray>(map->instance_descriptors(), isolate);
283   int own_property_count = map->EnumLength();
284   // If the enum length of the given map is set to kInvalidEnumCache, this
285   // means that the map itself has never used the present enum cache. The
286   // first step to using the cache is to set the enum length of the map by
287   // counting the number of own descriptors that are ENUMERABLE_STRINGS.
288   if (own_property_count == kInvalidEnumCacheSentinel) {
289     own_property_count =
290         map->NumberOfDescribedProperties(OWN_DESCRIPTORS, ENUMERABLE_STRINGS);
291   } else {
292     DCHECK(
293         own_property_count ==
294         map->NumberOfDescribedProperties(OWN_DESCRIPTORS, ENUMERABLE_STRINGS));
295   }
296 
297   if (descs->HasEnumCache()) {
298     Handle<FixedArray> keys(descs->GetEnumCache(), isolate);
299     // In case the number of properties required in the enum are actually
300     // present, we can reuse the enum cache. Otherwise, this means that the
301     // enum cache was generated for a previous (smaller) version of the
302     // Descriptor Array. In that case we regenerate the enum cache.
303     if (own_property_count <= keys->length()) {
304       isolate->counters()->enum_cache_hits()->Increment();
305       if (cache_enum_length) map->SetEnumLength(own_property_count);
306       return ReduceFixedArrayTo(isolate, keys, own_property_count);
307     }
308   }
309 
310   if (descs->IsEmpty()) {
311     isolate->counters()->enum_cache_hits()->Increment();
312     if (cache_enum_length) map->SetEnumLength(0);
313     return isolate->factory()->empty_fixed_array();
314   }
315 
316   isolate->counters()->enum_cache_misses()->Increment();
317 
318   Handle<FixedArray> storage =
319       isolate->factory()->NewFixedArray(own_property_count);
320   Handle<FixedArray> indices =
321       isolate->factory()->NewFixedArray(own_property_count);
322 
323   int size = map->NumberOfOwnDescriptors();
324   int index = 0;
325 
326   for (int i = 0; i < size; i++) {
327     PropertyDetails details = descs->GetDetails(i);
328     if (details.IsDontEnum()) continue;
329     Object* key = descs->GetKey(i);
330     if (key->IsSymbol()) continue;
331     storage->set(index, key);
332     if (!indices.is_null()) {
333       if (details.location() == kField) {
334         DCHECK_EQ(kData, details.kind());
335         FieldIndex field_index = FieldIndex::ForDescriptor(*map, i);
336         int load_by_field_index = field_index.GetLoadByFieldIndex();
337         indices->set(index, Smi::FromInt(load_by_field_index));
338       } else {
339         indices = Handle<FixedArray>();
340       }
341     }
342     index++;
343   }
344   DCHECK(index == storage->length());
345 
346   DescriptorArray::SetEnumCache(descs, isolate, storage, indices);
347   if (cache_enum_length) {
348     map->SetEnumLength(own_property_count);
349   }
350   return storage;
351 }
352 
353 template <bool fast_properties>
GetOwnKeysWithElements(Isolate * isolate,Handle<JSObject> object,GetKeysConversion convert)354 MaybeHandle<FixedArray> GetOwnKeysWithElements(Isolate* isolate,
355                                                Handle<JSObject> object,
356                                                GetKeysConversion convert) {
357   Handle<FixedArray> keys;
358   ElementsAccessor* accessor = object->GetElementsAccessor();
359   if (fast_properties) {
360     keys = GetFastEnumPropertyKeys(isolate, object);
361   } else {
362     // TODO(cbruni): preallocate big enough array to also hold elements.
363     keys = KeyAccumulator::GetOwnEnumPropertyKeys(isolate, object);
364   }
365   MaybeHandle<FixedArray> result =
366       accessor->PrependElementIndices(object, keys, convert, ONLY_ENUMERABLE);
367 
368   if (FLAG_trace_for_in_enumerate) {
369     PrintF("| strings=%d symbols=0 elements=%u || prototypes>=1 ||\n",
370            keys->length(), result.ToHandleChecked()->length() - keys->length());
371   }
372   return result;
373 }
374 
OnlyHasSimpleProperties(Map * map)375 bool OnlyHasSimpleProperties(Map* map) {
376   return map->instance_type() > LAST_CUSTOM_ELEMENTS_RECEIVER;
377 }
378 
379 }  // namespace
380 
GetKeys(GetKeysConversion keys_conversion)381 MaybeHandle<FixedArray> FastKeyAccumulator::GetKeys(
382     GetKeysConversion keys_conversion) {
383   if (filter_ == ENUMERABLE_STRINGS) {
384     Handle<FixedArray> keys;
385     if (GetKeysFast(keys_conversion).ToHandle(&keys)) {
386       return keys;
387     }
388     if (isolate_->has_pending_exception()) return MaybeHandle<FixedArray>();
389   }
390 
391   return GetKeysSlow(keys_conversion);
392 }
393 
GetKeysFast(GetKeysConversion keys_conversion)394 MaybeHandle<FixedArray> FastKeyAccumulator::GetKeysFast(
395     GetKeysConversion keys_conversion) {
396   bool own_only = has_empty_prototype_ || mode_ == KeyCollectionMode::kOwnOnly;
397   Map* map = receiver_->map();
398   if (!own_only || !OnlyHasSimpleProperties(map)) {
399     return MaybeHandle<FixedArray>();
400   }
401 
402   // From this point on we are certiain to only collect own keys.
403   DCHECK(receiver_->IsJSObject());
404   Handle<JSObject> object = Handle<JSObject>::cast(receiver_);
405 
406   // Do not try to use the enum-cache for dict-mode objects.
407   if (map->is_dictionary_map()) {
408     return GetOwnKeysWithElements<false>(isolate_, object, keys_conversion);
409   }
410   int enum_length = receiver_->map()->EnumLength();
411   if (enum_length == kInvalidEnumCacheSentinel) {
412     Handle<FixedArray> keys;
413     // Try initializing the enum cache and return own properties.
414     if (GetOwnKeysWithUninitializedEnumCache().ToHandle(&keys)) {
415       if (FLAG_trace_for_in_enumerate) {
416         PrintF("| strings=%d symbols=0 elements=0 || prototypes>=1 ||\n",
417                keys->length());
418       }
419       is_receiver_simple_enum_ =
420           object->map()->EnumLength() != kInvalidEnumCacheSentinel;
421       return keys;
422     }
423   }
424   // The properties-only case failed because there were probably elements on the
425   // receiver.
426   return GetOwnKeysWithElements<true>(isolate_, object, keys_conversion);
427 }
428 
429 MaybeHandle<FixedArray>
GetOwnKeysWithUninitializedEnumCache()430 FastKeyAccumulator::GetOwnKeysWithUninitializedEnumCache() {
431   Handle<JSObject> object = Handle<JSObject>::cast(receiver_);
432   // Uninitalized enum cache
433   Map* map = object->map();
434   if (object->elements()->length() != 0) {
435     // Assume that there are elements.
436     return MaybeHandle<FixedArray>();
437   }
438   int number_of_own_descriptors = map->NumberOfOwnDescriptors();
439   if (number_of_own_descriptors == 0) {
440     map->SetEnumLength(0);
441     return isolate_->factory()->empty_fixed_array();
442   }
443   // We have no elements but possibly enumerable property keys, hence we can
444   // directly initialize the enum cache.
445   Handle<FixedArray> keys = GetFastEnumPropertyKeys(isolate_, object);
446   if (is_for_in_) return keys;
447   // Do not leak the enum cache as it might end up as an elements backing store.
448   return isolate_->factory()->CopyFixedArray(keys);
449 }
450 
GetKeysSlow(GetKeysConversion keys_conversion)451 MaybeHandle<FixedArray> FastKeyAccumulator::GetKeysSlow(
452     GetKeysConversion keys_conversion) {
453   KeyAccumulator accumulator(isolate_, mode_, filter_);
454   accumulator.set_is_for_in(is_for_in_);
455   accumulator.set_last_non_empty_prototype(last_non_empty_prototype_);
456 
457   MAYBE_RETURN(accumulator.CollectKeys(receiver_, receiver_),
458                MaybeHandle<FixedArray>());
459   return accumulator.GetKeys(keys_conversion);
460 }
461 
462 namespace {
463 
464 enum IndexedOrNamed { kIndexed, kNamed };
465 
466 // Returns |true| on success, |nothing| on exception.
467 template <class Callback, IndexedOrNamed type>
CollectInterceptorKeysInternal(Handle<JSReceiver> receiver,Handle<JSObject> object,Handle<InterceptorInfo> interceptor,KeyAccumulator * accumulator)468 Maybe<bool> CollectInterceptorKeysInternal(Handle<JSReceiver> receiver,
469                                            Handle<JSObject> object,
470                                            Handle<InterceptorInfo> interceptor,
471                                            KeyAccumulator* accumulator) {
472   Isolate* isolate = accumulator->isolate();
473   PropertyCallbackArguments args(isolate, interceptor->data(), *receiver,
474                                  *object, Object::DONT_THROW);
475   Handle<JSObject> result;
476   if (!interceptor->enumerator()->IsUndefined(isolate)) {
477     Callback enum_fun = v8::ToCData<Callback>(interceptor->enumerator());
478     const char* log_tag = type == kIndexed ? "interceptor-indexed-enum"
479                                            : "interceptor-named-enum";
480     LOG(isolate, ApiObjectAccess(log_tag, *object));
481     result = args.Call(enum_fun);
482   }
483   RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, Nothing<bool>());
484   if (result.is_null()) return Just(true);
485   accumulator->AddKeys(
486       result, type == kIndexed ? CONVERT_TO_ARRAY_INDEX : DO_NOT_CONVERT);
487   return Just(true);
488 }
489 
490 template <class Callback, IndexedOrNamed type>
CollectInterceptorKeys(Handle<JSReceiver> receiver,Handle<JSObject> object,KeyAccumulator * accumulator)491 Maybe<bool> CollectInterceptorKeys(Handle<JSReceiver> receiver,
492                                    Handle<JSObject> object,
493                                    KeyAccumulator* accumulator) {
494   Isolate* isolate = accumulator->isolate();
495   if (type == kIndexed) {
496     if (!object->HasIndexedInterceptor()) return Just(true);
497   } else {
498     if (!object->HasNamedInterceptor()) return Just(true);
499   }
500   Handle<InterceptorInfo> interceptor(type == kIndexed
501                                           ? object->GetIndexedInterceptor()
502                                           : object->GetNamedInterceptor(),
503                                       isolate);
504   if ((accumulator->filter() & ONLY_ALL_CAN_READ) &&
505       !interceptor->all_can_read()) {
506     return Just(true);
507   }
508   return CollectInterceptorKeysInternal<Callback, type>(
509       receiver, object, interceptor, accumulator);
510 }
511 
512 }  // namespace
513 
CollectOwnElementIndices(Handle<JSReceiver> receiver,Handle<JSObject> object)514 Maybe<bool> KeyAccumulator::CollectOwnElementIndices(
515     Handle<JSReceiver> receiver, Handle<JSObject> object) {
516   if (filter_ & SKIP_STRINGS || skip_indices_) return Just(true);
517 
518   ElementsAccessor* accessor = object->GetElementsAccessor();
519   accessor->CollectElementIndices(object, this);
520 
521   return CollectInterceptorKeys<v8::IndexedPropertyEnumeratorCallback,
522                                 kIndexed>(receiver, object, this);
523 }
524 
525 namespace {
526 
527 template <bool skip_symbols>
CollectOwnPropertyNamesInternal(Handle<JSObject> object,KeyAccumulator * keys,Handle<DescriptorArray> descs,int start_index,int limit)528 int CollectOwnPropertyNamesInternal(Handle<JSObject> object,
529                                     KeyAccumulator* keys,
530                                     Handle<DescriptorArray> descs,
531                                     int start_index, int limit) {
532   int first_skipped = -1;
533   PropertyFilter filter = keys->filter();
534   KeyCollectionMode mode = keys->mode();
535   for (int i = start_index; i < limit; i++) {
536     bool is_shadowing_key = false;
537     PropertyDetails details = descs->GetDetails(i);
538 
539     if ((details.attributes() & filter) != 0) {
540       if (mode == KeyCollectionMode::kIncludePrototypes) {
541         is_shadowing_key = true;
542       } else {
543         continue;
544       }
545     }
546 
547     if (filter & ONLY_ALL_CAN_READ) {
548       if (details.kind() != kAccessor) continue;
549       Object* accessors = descs->GetValue(i);
550       if (!accessors->IsAccessorInfo()) continue;
551       if (!AccessorInfo::cast(accessors)->all_can_read()) continue;
552     }
553 
554     Name* key = descs->GetKey(i);
555     if (skip_symbols == key->IsSymbol()) {
556       if (first_skipped == -1) first_skipped = i;
557       continue;
558     }
559     if (key->FilterKey(keys->filter())) continue;
560 
561     if (is_shadowing_key) {
562       keys->AddShadowingKey(key);
563     } else {
564       keys->AddKey(key, DO_NOT_CONVERT);
565     }
566   }
567   return first_skipped;
568 }
569 
570 template <class T>
GetOwnEnumPropertyDictionaryKeys(Isolate * isolate,KeyCollectionMode mode,KeyAccumulator * accumulator,Handle<JSObject> object,T * raw_dictionary)571 Handle<FixedArray> GetOwnEnumPropertyDictionaryKeys(Isolate* isolate,
572                                                     KeyCollectionMode mode,
573                                                     KeyAccumulator* accumulator,
574                                                     Handle<JSObject> object,
575                                                     T* raw_dictionary) {
576   Handle<T> dictionary(raw_dictionary, isolate);
577   int length = dictionary->NumberOfEnumElements();
578   if (length == 0) {
579     return isolate->factory()->empty_fixed_array();
580   }
581   Handle<FixedArray> storage = isolate->factory()->NewFixedArray(length);
582   T::CopyEnumKeysTo(dictionary, storage, mode, accumulator);
583   return storage;
584 }
585 }  // namespace
586 
CollectOwnPropertyNames(Handle<JSReceiver> receiver,Handle<JSObject> object)587 Maybe<bool> KeyAccumulator::CollectOwnPropertyNames(Handle<JSReceiver> receiver,
588                                                     Handle<JSObject> object) {
589   if (filter_ == ENUMERABLE_STRINGS) {
590     Handle<FixedArray> enum_keys;
591     if (object->HasFastProperties()) {
592       enum_keys = KeyAccumulator::GetOwnEnumPropertyKeys(isolate_, object);
593       // If the number of properties equals the length of enumerable properties
594       // we do not have to filter out non-enumerable ones
595       Map* map = object->map();
596       int nof_descriptors = map->NumberOfOwnDescriptors();
597       if (enum_keys->length() != nof_descriptors) {
598         Handle<DescriptorArray> descs =
599             Handle<DescriptorArray>(map->instance_descriptors(), isolate_);
600         for (int i = 0; i < nof_descriptors; i++) {
601           PropertyDetails details = descs->GetDetails(i);
602           if (!details.IsDontEnum()) continue;
603           Object* key = descs->GetKey(i);
604           this->AddShadowingKey(key);
605         }
606       }
607     } else if (object->IsJSGlobalObject()) {
608       enum_keys = GetOwnEnumPropertyDictionaryKeys(
609           isolate_, mode_, this, object, object->global_dictionary());
610     } else {
611       enum_keys = GetOwnEnumPropertyDictionaryKeys(
612           isolate_, mode_, this, object, object->property_dictionary());
613     }
614     AddKeys(enum_keys, DO_NOT_CONVERT);
615   } else {
616     if (object->HasFastProperties()) {
617       int limit = object->map()->NumberOfOwnDescriptors();
618       Handle<DescriptorArray> descs(object->map()->instance_descriptors(),
619                                     isolate_);
620       // First collect the strings,
621       int first_symbol =
622           CollectOwnPropertyNamesInternal<true>(object, this, descs, 0, limit);
623       // then the symbols.
624       if (first_symbol != -1) {
625         CollectOwnPropertyNamesInternal<false>(object, this, descs,
626                                                first_symbol, limit);
627       }
628     } else if (object->IsJSGlobalObject()) {
629       GlobalDictionary::CollectKeysTo(
630           handle(object->global_dictionary(), isolate_), this);
631     } else {
632       NameDictionary::CollectKeysTo(
633           handle(object->property_dictionary(), isolate_), this);
634     }
635   }
636   // Add the property keys from the interceptor.
637   return CollectInterceptorKeys<v8::GenericNamedPropertyEnumeratorCallback,
638                                 kNamed>(receiver, object, this);
639 }
640 
CollectAccessCheckInterceptorKeys(Handle<AccessCheckInfo> access_check_info,Handle<JSReceiver> receiver,Handle<JSObject> object)641 Maybe<bool> KeyAccumulator::CollectAccessCheckInterceptorKeys(
642     Handle<AccessCheckInfo> access_check_info, Handle<JSReceiver> receiver,
643     Handle<JSObject> object) {
644   MAYBE_RETURN(
645       (CollectInterceptorKeysInternal<v8::IndexedPropertyEnumeratorCallback,
646                                       kIndexed>(
647           receiver, object,
648           handle(
649               InterceptorInfo::cast(access_check_info->indexed_interceptor()),
650               isolate_),
651           this)),
652       Nothing<bool>());
653   MAYBE_RETURN(
654       (CollectInterceptorKeysInternal<
655           v8::GenericNamedPropertyEnumeratorCallback, kNamed>(
656           receiver, object,
657           handle(InterceptorInfo::cast(access_check_info->named_interceptor()),
658                  isolate_),
659           this)),
660       Nothing<bool>());
661   return Just(true);
662 }
663 
664 // Returns |true| on success, |false| if prototype walking should be stopped,
665 // |nothing| if an exception was thrown.
CollectOwnKeys(Handle<JSReceiver> receiver,Handle<JSObject> object)666 Maybe<bool> KeyAccumulator::CollectOwnKeys(Handle<JSReceiver> receiver,
667                                            Handle<JSObject> object) {
668   // Check access rights if required.
669   if (object->IsAccessCheckNeeded() &&
670       !isolate_->MayAccess(handle(isolate_->context()), object)) {
671     // The cross-origin spec says that [[Enumerate]] shall return an empty
672     // iterator when it doesn't have access...
673     if (mode_ == KeyCollectionMode::kIncludePrototypes) {
674       return Just(false);
675     }
676     // ...whereas [[OwnPropertyKeys]] shall return whitelisted properties.
677     DCHECK(KeyCollectionMode::kOwnOnly == mode_);
678     Handle<AccessCheckInfo> access_check_info;
679     {
680       DisallowHeapAllocation no_gc;
681       AccessCheckInfo* maybe_info = AccessCheckInfo::Get(isolate_, object);
682       if (maybe_info) access_check_info = handle(maybe_info, isolate_);
683     }
684     // We always have both kinds of interceptors or none.
685     if (!access_check_info.is_null() &&
686         access_check_info->named_interceptor()) {
687       MAYBE_RETURN(CollectAccessCheckInterceptorKeys(access_check_info,
688                                                      receiver, object),
689                    Nothing<bool>());
690       return Just(false);
691     }
692     filter_ = static_cast<PropertyFilter>(filter_ | ONLY_ALL_CAN_READ);
693   }
694   MAYBE_RETURN(CollectOwnElementIndices(receiver, object), Nothing<bool>());
695   MAYBE_RETURN(CollectOwnPropertyNames(receiver, object), Nothing<bool>());
696   return Just(true);
697 }
698 
699 // static
GetOwnEnumPropertyKeys(Isolate * isolate,Handle<JSObject> object)700 Handle<FixedArray> KeyAccumulator::GetOwnEnumPropertyKeys(
701     Isolate* isolate, Handle<JSObject> object) {
702   if (object->HasFastProperties()) {
703     return GetFastEnumPropertyKeys(isolate, object);
704   } else if (object->IsJSGlobalObject()) {
705     return GetOwnEnumPropertyDictionaryKeys(
706         isolate, KeyCollectionMode::kOwnOnly, nullptr, object,
707         object->global_dictionary());
708   } else {
709     return GetOwnEnumPropertyDictionaryKeys(
710         isolate, KeyCollectionMode::kOwnOnly, nullptr, object,
711         object->property_dictionary());
712   }
713 }
714 
715 // ES6 9.5.12
716 // Returns |true| on success, |nothing| in case of exception.
CollectOwnJSProxyKeys(Handle<JSReceiver> receiver,Handle<JSProxy> proxy)717 Maybe<bool> KeyAccumulator::CollectOwnJSProxyKeys(Handle<JSReceiver> receiver,
718                                                   Handle<JSProxy> proxy) {
719   STACK_CHECK(isolate_, Nothing<bool>());
720   // 1. Let handler be the value of the [[ProxyHandler]] internal slot of O.
721   Handle<Object> handler(proxy->handler(), isolate_);
722   // 2. If handler is null, throw a TypeError exception.
723   // 3. Assert: Type(handler) is Object.
724   if (proxy->IsRevoked()) {
725     isolate_->Throw(*isolate_->factory()->NewTypeError(
726         MessageTemplate::kProxyRevoked, isolate_->factory()->ownKeys_string()));
727     return Nothing<bool>();
728   }
729   // 4. Let target be the value of the [[ProxyTarget]] internal slot of O.
730   Handle<JSReceiver> target(proxy->target(), isolate_);
731   // 5. Let trap be ? GetMethod(handler, "ownKeys").
732   Handle<Object> trap;
733   ASSIGN_RETURN_ON_EXCEPTION_VALUE(
734       isolate_, trap, Object::GetMethod(Handle<JSReceiver>::cast(handler),
735                                         isolate_->factory()->ownKeys_string()),
736       Nothing<bool>());
737   // 6. If trap is undefined, then
738   if (trap->IsUndefined(isolate_)) {
739     // 6a. Return target.[[OwnPropertyKeys]]().
740     return CollectOwnJSProxyTargetKeys(proxy, target);
741   }
742   // 7. Let trapResultArray be Call(trap, handler, «target»).
743   Handle<Object> trap_result_array;
744   Handle<Object> args[] = {target};
745   ASSIGN_RETURN_ON_EXCEPTION_VALUE(
746       isolate_, trap_result_array,
747       Execution::Call(isolate_, trap, handler, arraysize(args), args),
748       Nothing<bool>());
749   // 8. Let trapResult be ? CreateListFromArrayLike(trapResultArray,
750   //    «String, Symbol»).
751   Handle<FixedArray> trap_result;
752   ASSIGN_RETURN_ON_EXCEPTION_VALUE(
753       isolate_, trap_result,
754       Object::CreateListFromArrayLike(isolate_, trap_result_array,
755                                       ElementTypes::kStringAndSymbol),
756       Nothing<bool>());
757   // 9. Let extensibleTarget be ? IsExtensible(target).
758   Maybe<bool> maybe_extensible = JSReceiver::IsExtensible(target);
759   MAYBE_RETURN(maybe_extensible, Nothing<bool>());
760   bool extensible_target = maybe_extensible.FromJust();
761   // 10. Let targetKeys be ? target.[[OwnPropertyKeys]]().
762   Handle<FixedArray> target_keys;
763   ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate_, target_keys,
764                                    JSReceiver::OwnPropertyKeys(target),
765                                    Nothing<bool>());
766   // 11. (Assert)
767   // 12. Let targetConfigurableKeys be an empty List.
768   // To save memory, we're re-using target_keys and will modify it in-place.
769   Handle<FixedArray> target_configurable_keys = target_keys;
770   // 13. Let targetNonconfigurableKeys be an empty List.
771   Handle<FixedArray> target_nonconfigurable_keys =
772       isolate_->factory()->NewFixedArray(target_keys->length());
773   int nonconfigurable_keys_length = 0;
774   // 14. Repeat, for each element key of targetKeys:
775   for (int i = 0; i < target_keys->length(); ++i) {
776     // 14a. Let desc be ? target.[[GetOwnProperty]](key).
777     PropertyDescriptor desc;
778     Maybe<bool> found = JSReceiver::GetOwnPropertyDescriptor(
779         isolate_, target, handle(target_keys->get(i), isolate_), &desc);
780     MAYBE_RETURN(found, Nothing<bool>());
781     // 14b. If desc is not undefined and desc.[[Configurable]] is false, then
782     if (found.FromJust() && !desc.configurable()) {
783       // 14b i. Append key as an element of targetNonconfigurableKeys.
784       target_nonconfigurable_keys->set(nonconfigurable_keys_length,
785                                        target_keys->get(i));
786       nonconfigurable_keys_length++;
787       // The key was moved, null it out in the original list.
788       target_keys->set(i, Smi::kZero);
789     } else {
790       // 14c. Else,
791       // 14c i. Append key as an element of targetConfigurableKeys.
792       // (No-op, just keep it in |target_keys|.)
793     }
794   }
795   // 15. If extensibleTarget is true and targetNonconfigurableKeys is empty,
796   //     then:
797   if (extensible_target && nonconfigurable_keys_length == 0) {
798     // 15a. Return trapResult.
799     return AddKeysFromJSProxy(proxy, trap_result);
800   }
801   // 16. Let uncheckedResultKeys be a new List which is a copy of trapResult.
802   Zone set_zone(isolate_->allocator(), ZONE_NAME);
803   const int kPresent = 1;
804   const int kGone = 0;
805   IdentityMap<int, ZoneAllocationPolicy> unchecked_result_keys(
806       isolate_->heap(), ZoneAllocationPolicy(&set_zone));
807   int unchecked_result_keys_size = 0;
808   for (int i = 0; i < trap_result->length(); ++i) {
809     DCHECK(trap_result->get(i)->IsUniqueName());
810     Object* key = trap_result->get(i);
811     int* entry = unchecked_result_keys.Get(key);
812     if (*entry != kPresent) {
813       *entry = kPresent;
814       unchecked_result_keys_size++;
815     }
816   }
817   // 17. Repeat, for each key that is an element of targetNonconfigurableKeys:
818   for (int i = 0; i < nonconfigurable_keys_length; ++i) {
819     Object* key = target_nonconfigurable_keys->get(i);
820     // 17a. If key is not an element of uncheckedResultKeys, throw a
821     //      TypeError exception.
822     int* found = unchecked_result_keys.Find(key);
823     if (found == nullptr || *found == kGone) {
824       isolate_->Throw(*isolate_->factory()->NewTypeError(
825           MessageTemplate::kProxyOwnKeysMissing, handle(key, isolate_)));
826       return Nothing<bool>();
827     }
828     // 17b. Remove key from uncheckedResultKeys.
829     *found = kGone;
830     unchecked_result_keys_size--;
831   }
832   // 18. If extensibleTarget is true, return trapResult.
833   if (extensible_target) {
834     return AddKeysFromJSProxy(proxy, trap_result);
835   }
836   // 19. Repeat, for each key that is an element of targetConfigurableKeys:
837   for (int i = 0; i < target_configurable_keys->length(); ++i) {
838     Object* key = target_configurable_keys->get(i);
839     if (key->IsSmi()) continue;  // Zapped entry, was nonconfigurable.
840     // 19a. If key is not an element of uncheckedResultKeys, throw a
841     //      TypeError exception.
842     int* found = unchecked_result_keys.Find(key);
843     if (found == nullptr || *found == kGone) {
844       isolate_->Throw(*isolate_->factory()->NewTypeError(
845           MessageTemplate::kProxyOwnKeysMissing, handle(key, isolate_)));
846       return Nothing<bool>();
847     }
848     // 19b. Remove key from uncheckedResultKeys.
849     *found = kGone;
850     unchecked_result_keys_size--;
851   }
852   // 20. If uncheckedResultKeys is not empty, throw a TypeError exception.
853   if (unchecked_result_keys_size != 0) {
854     DCHECK_GT(unchecked_result_keys_size, 0);
855     isolate_->Throw(*isolate_->factory()->NewTypeError(
856         MessageTemplate::kProxyOwnKeysNonExtensible));
857     return Nothing<bool>();
858   }
859   // 21. Return trapResult.
860   return AddKeysFromJSProxy(proxy, trap_result);
861 }
862 
CollectOwnJSProxyTargetKeys(Handle<JSProxy> proxy,Handle<JSReceiver> target)863 Maybe<bool> KeyAccumulator::CollectOwnJSProxyTargetKeys(
864     Handle<JSProxy> proxy, Handle<JSReceiver> target) {
865   // TODO(cbruni): avoid creating another KeyAccumulator
866   Handle<FixedArray> keys;
867   ASSIGN_RETURN_ON_EXCEPTION_VALUE(
868       isolate_, keys,
869       KeyAccumulator::GetKeys(target, KeyCollectionMode::kOwnOnly, filter_,
870                               GetKeysConversion::kConvertToString, is_for_in_),
871       Nothing<bool>());
872   Maybe<bool> result = AddKeysFromJSProxy(proxy, keys);
873   return result;
874 }
875 
876 }  // namespace internal
877 }  // namespace v8
878