• 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-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