V8 API Reference, 7.2.502.16 (for Deno 0.2.4)
keys.cc
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 
23 namespace {
24 
25 static bool ContainsOnlyValidKeys(Handle<FixedArray> array) {
26  int len = array->length();
27  for (int i = 0; i < len; i++) {
28  Object* e = array->get(i);
29  if (!(e->IsName() || e->IsNumber())) return false;
30  }
31  return true;
32 }
33 
34 } // namespace
35 
36 // static
37 MaybeHandle<FixedArray> KeyAccumulator::GetKeys(
38  Handle<JSReceiver> object, KeyCollectionMode mode, PropertyFilter filter,
39  GetKeysConversion keys_conversion, bool is_for_in, bool skip_indices) {
40  Isolate* isolate = object->GetIsolate();
41  FastKeyAccumulator accumulator(isolate, object, mode, filter, is_for_in,
42  skip_indices);
43  return accumulator.GetKeys(keys_conversion);
44 }
45 
46 Handle<FixedArray> KeyAccumulator::GetKeys(GetKeysConversion convert) {
47  if (keys_.is_null()) {
48  return isolate_->factory()->empty_fixed_array();
49  }
50  if (mode_ == KeyCollectionMode::kOwnOnly &&
51  keys_->map() == ReadOnlyRoots(isolate_).fixed_array_map()) {
52  return Handle<FixedArray>::cast(keys_);
53  }
54  USE(ContainsOnlyValidKeys);
55  Handle<FixedArray> result =
56  OrderedHashSet::ConvertToKeysArray(isolate(), keys(), convert);
57  DCHECK(ContainsOnlyValidKeys(result));
58  return result;
59 }
60 
61 Handle<OrderedHashSet> KeyAccumulator::keys() {
62  return Handle<OrderedHashSet>::cast(keys_);
63 }
64 
65 void KeyAccumulator::AddKey(Object* key, AddKeyConversion convert) {
66  AddKey(handle(key, isolate_), convert);
67 }
68 
69 void KeyAccumulator::AddKey(Handle<Object> key, AddKeyConversion convert) {
70  if (key->IsSymbol()) {
71  if (filter_ & SKIP_SYMBOLS) return;
72  if (Handle<Symbol>::cast(key)->is_private()) return;
73  } else if (filter_ & SKIP_STRINGS) {
74  return;
75  }
76  if (IsShadowed(key)) return;
77  if (keys_.is_null()) {
78  // TODO(3770): Drop explicit conversion.
79  keys_ =
80  Handle<FixedArray>(OrderedHashSet::Allocate(isolate_, 16).location());
81  }
82  uint32_t index;
83  if (convert == CONVERT_TO_ARRAY_INDEX && key->IsString() &&
84  Handle<String>::cast(key)->AsArrayIndex(&index)) {
85  key = isolate_->factory()->NewNumberFromUint(index);
86  }
87  Handle<OrderedHashSet> new_set = OrderedHashSet::Add(isolate(), keys(), key);
88  if (*new_set != *keys_) {
89  // The keys_ Set is converted directly to a FixedArray in GetKeys which can
90  // be left-trimmer. Hence the previous Set should not keep a pointer to the
91  // new one.
92  keys_->set(OrderedHashSet::kNextTableIndex, Smi::kZero);
93  // TODO(3770): Drop explicit conversion.
94  keys_ = Handle<FixedArray>(new_set.location());
95  }
96 }
97 
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 
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 
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.
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 
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 
208 bool KeyAccumulator::HasShadowingKeys() { return !shadowing_keys_.is_null(); }
209 
210 bool KeyAccumulator::IsShadowed(Handle<Object> key) {
211  if (!HasShadowingKeys() || skip_shadow_check_) return false;
212  return shadowing_keys_->Has(isolate_, key);
213 }
214 
215 void KeyAccumulator::AddShadowingKey(Object* key) {
216  if (mode_ == KeyCollectionMode::kOwnOnly) return;
217  AddShadowingKey(handle(key, isolate_));
218 }
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 
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 
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 
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 
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.
285 Handle<FixedArray> GetFastEnumPropertyKeys(Isolate* isolate,
286  Handle<JSObject> object) {
287  Handle<Map> map(object->map(), isolate);
288  Handle<FixedArray> keys(map->instance_descriptors()->enum_cache()->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::InitializeOrChangeEnumCache(descriptors, isolate, keys,
355  indices);
356  if (map->OnlyHasSimpleProperties()) map->SetEnumLength(enum_length);
357 
358  return keys;
359 }
360 
361 template <bool fast_properties>
362 MaybeHandle<FixedArray> GetOwnKeysWithElements(Isolate* isolate,
363  Handle<JSObject> object,
364  GetKeysConversion convert,
365  bool skip_indices) {
366  Handle<FixedArray> keys;
367  ElementsAccessor* accessor = object->GetElementsAccessor();
368  if (fast_properties) {
369  keys = GetFastEnumPropertyKeys(isolate, object);
370  } else {
371  // TODO(cbruni): preallocate big enough array to also hold elements.
372  keys = KeyAccumulator::GetOwnEnumPropertyKeys(isolate, object);
373  }
374 
375  MaybeHandle<FixedArray> result;
376  if (skip_indices) {
377  result = keys;
378  } else {
379  result =
380  accessor->PrependElementIndices(object, keys, convert, ONLY_ENUMERABLE);
381  }
382 
383  if (FLAG_trace_for_in_enumerate) {
384  PrintF("| strings=%d symbols=0 elements=%u || prototypes>=1 ||\n",
385  keys->length(), result.ToHandleChecked()->length() - keys->length());
386  }
387  return result;
388 }
389 
390 } // namespace
391 
392 MaybeHandle<FixedArray> FastKeyAccumulator::GetKeys(
393  GetKeysConversion keys_conversion) {
394  if (filter_ == ENUMERABLE_STRINGS) {
395  Handle<FixedArray> keys;
396  if (GetKeysFast(keys_conversion).ToHandle(&keys)) {
397  return keys;
398  }
399  if (isolate_->has_pending_exception()) return MaybeHandle<FixedArray>();
400  }
401 
402  return GetKeysSlow(keys_conversion);
403 }
404 
405 MaybeHandle<FixedArray> FastKeyAccumulator::GetKeysFast(
406  GetKeysConversion keys_conversion) {
407  bool own_only = has_empty_prototype_ || mode_ == KeyCollectionMode::kOwnOnly;
408  Map map = receiver_->map();
409  if (!own_only || map->IsCustomElementsReceiverMap()) {
410  return MaybeHandle<FixedArray>();
411  }
412 
413  // From this point on we are certain to only collect own keys.
414  DCHECK(receiver_->IsJSObject());
415  Handle<JSObject> object = Handle<JSObject>::cast(receiver_);
416 
417  // Do not try to use the enum-cache for dict-mode objects.
418  if (map->is_dictionary_map()) {
419  return GetOwnKeysWithElements<false>(isolate_, object, keys_conversion,
420  skip_indices_);
421  }
422  int enum_length = receiver_->map()->EnumLength();
423  if (enum_length == kInvalidEnumCacheSentinel) {
424  Handle<FixedArray> keys;
425  // Try initializing the enum cache and return own properties.
426  if (GetOwnKeysWithUninitializedEnumCache().ToHandle(&keys)) {
427  if (FLAG_trace_for_in_enumerate) {
428  PrintF("| strings=%d symbols=0 elements=0 || prototypes>=1 ||\n",
429  keys->length());
430  }
431  is_receiver_simple_enum_ =
432  object->map()->EnumLength() != kInvalidEnumCacheSentinel;
433  return keys;
434  }
435  }
436  // The properties-only case failed because there were probably elements on the
437  // receiver.
438  return GetOwnKeysWithElements<true>(isolate_, object, keys_conversion,
439  skip_indices_);
440 }
441 
442 MaybeHandle<FixedArray>
443 FastKeyAccumulator::GetOwnKeysWithUninitializedEnumCache() {
444  Handle<JSObject> object = Handle<JSObject>::cast(receiver_);
445  // Uninitalized enum cache
446  Map map = object->map();
447  if (object->elements()->length() != 0) {
448  // Assume that there are elements.
449  return MaybeHandle<FixedArray>();
450  }
451  int number_of_own_descriptors = map->NumberOfOwnDescriptors();
452  if (number_of_own_descriptors == 0) {
453  map->SetEnumLength(0);
454  return isolate_->factory()->empty_fixed_array();
455  }
456  // We have no elements but possibly enumerable property keys, hence we can
457  // directly initialize the enum cache.
458  Handle<FixedArray> keys = GetFastEnumPropertyKeys(isolate_, object);
459  if (is_for_in_) return keys;
460  // Do not leak the enum cache as it might end up as an elements backing store.
461  return isolate_->factory()->CopyFixedArray(keys);
462 }
463 
464 MaybeHandle<FixedArray> FastKeyAccumulator::GetKeysSlow(
465  GetKeysConversion keys_conversion) {
466  KeyAccumulator accumulator(isolate_, mode_, filter_);
467  accumulator.set_is_for_in(is_for_in_);
468  accumulator.set_skip_indices(skip_indices_);
469  accumulator.set_last_non_empty_prototype(last_non_empty_prototype_);
470 
471  MAYBE_RETURN(accumulator.CollectKeys(receiver_, receiver_),
472  MaybeHandle<FixedArray>());
473  return accumulator.GetKeys(keys_conversion);
474 }
475 
476 namespace {
477 
478 enum IndexedOrNamed { kIndexed, kNamed };
479 
480 void FilterForEnumerableProperties(Handle<JSReceiver> receiver,
481  Handle<JSObject> object,
482  Handle<InterceptorInfo> interceptor,
483  KeyAccumulator* accumulator,
484  Handle<JSObject> result,
485  IndexedOrNamed type) {
486  DCHECK(result->IsJSArray() || result->HasSloppyArgumentsElements());
487  ElementsAccessor* accessor = result->GetElementsAccessor();
488 
489  uint32_t length = accessor->GetCapacity(*result, result->elements());
490  for (uint32_t i = 0; i < length; i++) {
491  if (!accessor->HasEntry(*result, i)) continue;
492 
493  // args are invalid after args.Call(), create a new one in every iteration.
494  PropertyCallbackArguments args(accumulator->isolate(), interceptor->data(),
495  *receiver, *object, kDontThrow);
496 
497  Handle<Object> element = accessor->Get(result, i);
498  Handle<Object> attributes;
499  if (type == kIndexed) {
500  uint32_t number;
501  CHECK(element->ToUint32(&number));
502  attributes = args.CallIndexedQuery(interceptor, number);
503  } else {
504  CHECK(element->IsName());
505  attributes =
506  args.CallNamedQuery(interceptor, Handle<Name>::cast(element));
507  }
508 
509  if (!attributes.is_null()) {
510  int32_t value;
511  CHECK(attributes->ToInt32(&value));
512  if ((value & DONT_ENUM) == 0) {
513  accumulator->AddKey(element, DO_NOT_CONVERT);
514  }
515  }
516  }
517 }
518 
519 // Returns |true| on success, |nothing| on exception.
520 Maybe<bool> CollectInterceptorKeysInternal(Handle<JSReceiver> receiver,
521  Handle<JSObject> object,
522  Handle<InterceptorInfo> interceptor,
523  KeyAccumulator* accumulator,
524  IndexedOrNamed type) {
525  Isolate* isolate = accumulator->isolate();
526  PropertyCallbackArguments enum_args(isolate, interceptor->data(), *receiver,
527  *object, kDontThrow);
528 
529  Handle<JSObject> result;
530  if (!interceptor->enumerator()->IsUndefined(isolate)) {
531  if (type == kIndexed) {
532  result = enum_args.CallIndexedEnumerator(interceptor);
533  } else {
534  DCHECK_EQ(type, kNamed);
535  result = enum_args.CallNamedEnumerator(interceptor);
536  }
537  }
538  RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, Nothing<bool>());
539  if (result.is_null()) return Just(true);
540 
541  if ((accumulator->filter() & ONLY_ENUMERABLE) &&
542  !interceptor->query()->IsUndefined(isolate)) {
543  FilterForEnumerableProperties(receiver, object, interceptor, accumulator,
544  result, type);
545  } else {
546  accumulator->AddKeys(
547  result, type == kIndexed ? CONVERT_TO_ARRAY_INDEX : DO_NOT_CONVERT);
548  }
549  return Just(true);
550 }
551 
552 Maybe<bool> CollectInterceptorKeys(Handle<JSReceiver> receiver,
553  Handle<JSObject> object,
554  KeyAccumulator* accumulator,
555  IndexedOrNamed type) {
556  Isolate* isolate = accumulator->isolate();
557  if (type == kIndexed) {
558  if (!object->HasIndexedInterceptor()) return Just(true);
559  } else {
560  if (!object->HasNamedInterceptor()) return Just(true);
561  }
562  Handle<InterceptorInfo> interceptor(type == kIndexed
563  ? object->GetIndexedInterceptor()
564  : object->GetNamedInterceptor(),
565  isolate);
566  if ((accumulator->filter() & ONLY_ALL_CAN_READ) &&
567  !interceptor->all_can_read()) {
568  return Just(true);
569  }
570  return CollectInterceptorKeysInternal(receiver, object, interceptor,
571  accumulator, type);
572 }
573 
574 } // namespace
575 
576 Maybe<bool> KeyAccumulator::CollectOwnElementIndices(
577  Handle<JSReceiver> receiver, Handle<JSObject> object) {
578  if (filter_ & SKIP_STRINGS || skip_indices_) return Just(true);
579 
580  ElementsAccessor* accessor = object->GetElementsAccessor();
581  accessor->CollectElementIndices(object, this);
582 
583  return CollectInterceptorKeys(receiver, object, this, kIndexed);
584 }
585 
586 namespace {
587 
588 template <bool skip_symbols>
589 int CollectOwnPropertyNamesInternal(Handle<JSObject> object,
590  KeyAccumulator* keys,
591  Handle<DescriptorArray> descs,
592  int start_index, int limit) {
593  int first_skipped = -1;
594  PropertyFilter filter = keys->filter();
595  KeyCollectionMode mode = keys->mode();
596  for (int i = start_index; i < limit; i++) {
597  bool is_shadowing_key = false;
598  PropertyDetails details = descs->GetDetails(i);
599 
600  if ((details.attributes() & filter) != 0) {
601  if (mode == KeyCollectionMode::kIncludePrototypes) {
602  is_shadowing_key = true;
603  } else {
604  continue;
605  }
606  }
607 
608  if (filter & ONLY_ALL_CAN_READ) {
609  if (details.kind() != kAccessor) continue;
610  Object* accessors = descs->GetStrongValue(i);
611  if (!accessors->IsAccessorInfo()) continue;
612  if (!AccessorInfo::cast(accessors)->all_can_read()) continue;
613  }
614 
615  Name key = descs->GetKey(i);
616  if (skip_symbols == key->IsSymbol()) {
617  if (first_skipped == -1) first_skipped = i;
618  continue;
619  }
620  if (key->FilterKey(keys->filter())) continue;
621 
622  if (is_shadowing_key) {
623  keys->AddShadowingKey(key);
624  } else {
625  keys->AddKey(key, DO_NOT_CONVERT);
626  }
627  }
628  return first_skipped;
629 }
630 
631 template <class T>
632 Handle<FixedArray> GetOwnEnumPropertyDictionaryKeys(Isolate* isolate,
633  KeyCollectionMode mode,
634  KeyAccumulator* accumulator,
635  Handle<JSObject> object,
636  T raw_dictionary) {
637  Handle<T> dictionary(raw_dictionary, isolate);
638  if (dictionary->NumberOfElements() == 0) {
639  return isolate->factory()->empty_fixed_array();
640  }
641  int length = dictionary->NumberOfEnumerableProperties();
642  Handle<FixedArray> storage = isolate->factory()->NewFixedArray(length);
643  T::CopyEnumKeysTo(isolate, dictionary, storage, mode, accumulator);
644  return storage;
645 }
646 } // namespace
647 
648 Maybe<bool> KeyAccumulator::CollectOwnPropertyNames(Handle<JSReceiver> receiver,
649  Handle<JSObject> object) {
650  if (filter_ == ENUMERABLE_STRINGS) {
651  Handle<FixedArray> enum_keys;
652  if (object->HasFastProperties()) {
653  enum_keys = KeyAccumulator::GetOwnEnumPropertyKeys(isolate_, object);
654  // If the number of properties equals the length of enumerable properties
655  // we do not have to filter out non-enumerable ones
656  Map map = object->map();
657  int nof_descriptors = map->NumberOfOwnDescriptors();
658  if (enum_keys->length() != nof_descriptors) {
659  Handle<DescriptorArray> descs =
660  Handle<DescriptorArray>(map->instance_descriptors(), isolate_);
661  for (int i = 0; i < nof_descriptors; i++) {
662  PropertyDetails details = descs->GetDetails(i);
663  if (!details.IsDontEnum()) continue;
664  Object* key = descs->GetKey(i);
665  this->AddShadowingKey(key);
666  }
667  }
668  } else if (object->IsJSGlobalObject()) {
669  enum_keys = GetOwnEnumPropertyDictionaryKeys(
670  isolate_, mode_, this, object,
671  JSGlobalObject::cast(*object)->global_dictionary());
672  } else {
673  enum_keys = GetOwnEnumPropertyDictionaryKeys(
674  isolate_, mode_, this, object, object->property_dictionary());
675  }
676  if (object->IsJSModuleNamespace()) {
677  // Simulate [[GetOwnProperty]] for establishing enumerability, which
678  // throws for uninitialized exports.
679  for (int i = 0, n = enum_keys->length(); i < n; ++i) {
680  Handle<String> key(String::cast(enum_keys->get(i)), isolate_);
681  if (Handle<JSModuleNamespace>::cast(object)
682  ->GetExport(isolate(), key)
683  .is_null()) {
684  return Nothing<bool>();
685  }
686  }
687  }
688  AddKeys(enum_keys, DO_NOT_CONVERT);
689  } else {
690  if (object->HasFastProperties()) {
691  int limit = object->map()->NumberOfOwnDescriptors();
692  Handle<DescriptorArray> descs(object->map()->instance_descriptors(),
693  isolate_);
694  // First collect the strings,
695  int first_symbol =
696  CollectOwnPropertyNamesInternal<true>(object, this, descs, 0, limit);
697  // then the symbols.
698  if (first_symbol != -1) {
699  CollectOwnPropertyNamesInternal<false>(object, this, descs,
700  first_symbol, limit);
701  }
702  } else if (object->IsJSGlobalObject()) {
703  GlobalDictionary::CollectKeysTo(
704  handle(JSGlobalObject::cast(*object)->global_dictionary(), isolate_),
705  this);
706  } else {
707  NameDictionary::CollectKeysTo(
708  handle(object->property_dictionary(), isolate_), this);
709  }
710  }
711  // Add the property keys from the interceptor.
712  return CollectInterceptorKeys(receiver, object, this, kNamed);
713 }
714 
715 Maybe<bool> KeyAccumulator::CollectAccessCheckInterceptorKeys(
716  Handle<AccessCheckInfo> access_check_info, Handle<JSReceiver> receiver,
717  Handle<JSObject> object) {
718  if (!skip_indices_) {
719  MAYBE_RETURN((CollectInterceptorKeysInternal(
720  receiver, object,
721  handle(InterceptorInfo::cast(
722  access_check_info->indexed_interceptor()),
723  isolate_),
724  this, kIndexed)),
725  Nothing<bool>());
726  }
727  MAYBE_RETURN(
728  (CollectInterceptorKeysInternal(
729  receiver, object,
730  handle(InterceptorInfo::cast(access_check_info->named_interceptor()),
731  isolate_),
732  this, kNamed)),
733  Nothing<bool>());
734  return Just(true);
735 }
736 
737 // Returns |true| on success, |false| if prototype walking should be stopped,
738 // |nothing| if an exception was thrown.
739 Maybe<bool> KeyAccumulator::CollectOwnKeys(Handle<JSReceiver> receiver,
740  Handle<JSObject> object) {
741  // Check access rights if required.
742  if (object->IsAccessCheckNeeded() &&
743  !isolate_->MayAccess(handle(isolate_->context(), isolate_), object)) {
744  // The cross-origin spec says that [[Enumerate]] shall return an empty
745  // iterator when it doesn't have access...
746  if (mode_ == KeyCollectionMode::kIncludePrototypes) {
747  return Just(false);
748  }
749  // ...whereas [[OwnPropertyKeys]] shall return whitelisted properties.
750  DCHECK_EQ(KeyCollectionMode::kOwnOnly, mode_);
751  Handle<AccessCheckInfo> access_check_info;
752  {
753  DisallowHeapAllocation no_gc;
754  AccessCheckInfo* maybe_info = AccessCheckInfo::Get(isolate_, object);
755  if (maybe_info) access_check_info = handle(maybe_info, isolate_);
756  }
757  // We always have both kinds of interceptors or none.
758  if (!access_check_info.is_null() &&
759  access_check_info->named_interceptor()) {
760  MAYBE_RETURN(CollectAccessCheckInterceptorKeys(access_check_info,
761  receiver, object),
762  Nothing<bool>());
763  return Just(false);
764  }
765  filter_ = static_cast<PropertyFilter>(filter_ | ONLY_ALL_CAN_READ);
766  }
767  MAYBE_RETURN(CollectOwnElementIndices(receiver, object), Nothing<bool>());
768  MAYBE_RETURN(CollectOwnPropertyNames(receiver, object), Nothing<bool>());
769  return Just(true);
770 }
771 
772 // static
773 Handle<FixedArray> KeyAccumulator::GetOwnEnumPropertyKeys(
774  Isolate* isolate, Handle<JSObject> object) {
775  if (object->HasFastProperties()) {
776  return GetFastEnumPropertyKeys(isolate, object);
777  } else if (object->IsJSGlobalObject()) {
778  return GetOwnEnumPropertyDictionaryKeys(
779  isolate, KeyCollectionMode::kOwnOnly, nullptr, object,
780  JSGlobalObject::cast(*object)->global_dictionary());
781  } else {
782  return GetOwnEnumPropertyDictionaryKeys(
783  isolate, KeyCollectionMode::kOwnOnly, nullptr, object,
784  object->property_dictionary());
785  }
786 }
787 
788 namespace {
789 
790 class NameComparator {
791  public:
792  explicit NameComparator(Isolate* isolate) : isolate_(isolate) {}
793 
794  bool operator()(uint32_t hash1, uint32_t hash2, const Handle<Name>& key1,
795  const Handle<Name>& key2) const {
796  return Name::Equals(isolate_, key1, key2);
797  }
798 
799  private:
800  Isolate* isolate_;
801 };
802 
803 } // namespace
804 
805 // ES6 9.5.12
806 // Returns |true| on success, |nothing| in case of exception.
807 Maybe<bool> KeyAccumulator::CollectOwnJSProxyKeys(Handle<JSReceiver> receiver,
808  Handle<JSProxy> proxy) {
809  STACK_CHECK(isolate_, Nothing<bool>());
810  // 1. Let handler be the value of the [[ProxyHandler]] internal slot of O.
811  Handle<Object> handler(proxy->handler(), isolate_);
812  // 2. If handler is null, throw a TypeError exception.
813  // 3. Assert: Type(handler) is Object.
814  if (proxy->IsRevoked()) {
815  isolate_->Throw(*isolate_->factory()->NewTypeError(
816  MessageTemplate::kProxyRevoked, isolate_->factory()->ownKeys_string()));
817  return Nothing<bool>();
818  }
819  // 4. Let target be the value of the [[ProxyTarget]] internal slot of O.
820  Handle<JSReceiver> target(JSReceiver::cast(proxy->target()), isolate_);
821  // 5. Let trap be ? GetMethod(handler, "ownKeys").
822  Handle<Object> trap;
823  ASSIGN_RETURN_ON_EXCEPTION_VALUE(
824  isolate_, trap, Object::GetMethod(Handle<JSReceiver>::cast(handler),
825  isolate_->factory()->ownKeys_string()),
826  Nothing<bool>());
827  // 6. If trap is undefined, then
828  if (trap->IsUndefined(isolate_)) {
829  // 6a. Return target.[[OwnPropertyKeys]]().
830  return CollectOwnJSProxyTargetKeys(proxy, target);
831  }
832  // 7. Let trapResultArray be Call(trap, handler, «target»).
833  Handle<Object> trap_result_array;
834  Handle<Object> args[] = {target};
835  ASSIGN_RETURN_ON_EXCEPTION_VALUE(
836  isolate_, trap_result_array,
837  Execution::Call(isolate_, trap, handler, arraysize(args), args),
838  Nothing<bool>());
839  // 8. Let trapResult be ? CreateListFromArrayLike(trapResultArray,
840  // «String, Symbol»).
841  Handle<FixedArray> trap_result;
842  ASSIGN_RETURN_ON_EXCEPTION_VALUE(
843  isolate_, trap_result,
844  Object::CreateListFromArrayLike(isolate_, trap_result_array,
845  ElementTypes::kStringAndSymbol),
846  Nothing<bool>());
847  // 9. Let extensibleTarget be ? IsExtensible(target).
848  Maybe<bool> maybe_extensible = JSReceiver::IsExtensible(target);
849  MAYBE_RETURN(maybe_extensible, Nothing<bool>());
850  bool extensible_target = maybe_extensible.FromJust();
851  // 10. Let targetKeys be ? target.[[OwnPropertyKeys]]().
852  Handle<FixedArray> target_keys;
853  ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate_, target_keys,
854  JSReceiver::OwnPropertyKeys(target),
855  Nothing<bool>());
856  // 11. (Assert)
857  // 12. Let targetConfigurableKeys be an empty List.
858  // To save memory, we're re-using target_keys and will modify it in-place.
859  Handle<FixedArray> target_configurable_keys = target_keys;
860  // 13. Let targetNonconfigurableKeys be an empty List.
861  Handle<FixedArray> target_nonconfigurable_keys =
862  isolate_->factory()->NewFixedArray(target_keys->length());
863  int nonconfigurable_keys_length = 0;
864  // 14. Repeat, for each element key of targetKeys:
865  for (int i = 0; i < target_keys->length(); ++i) {
866  // 14a. Let desc be ? target.[[GetOwnProperty]](key).
867  PropertyDescriptor desc;
868  Maybe<bool> found = JSReceiver::GetOwnPropertyDescriptor(
869  isolate_, target, handle(target_keys->get(i), isolate_), &desc);
870  MAYBE_RETURN(found, Nothing<bool>());
871  // 14b. If desc is not undefined and desc.[[Configurable]] is false, then
872  if (found.FromJust() && !desc.configurable()) {
873  // 14b i. Append key as an element of targetNonconfigurableKeys.
874  target_nonconfigurable_keys->set(nonconfigurable_keys_length,
875  target_keys->get(i));
876  nonconfigurable_keys_length++;
877  // The key was moved, null it out in the original list.
878  target_keys->set(i, Smi::kZero);
879  } else {
880  // 14c. Else,
881  // 14c i. Append key as an element of targetConfigurableKeys.
882  // (No-op, just keep it in |target_keys|.)
883  }
884  }
885  // 15. If extensibleTarget is true and targetNonconfigurableKeys is empty,
886  // then:
887  if (extensible_target && nonconfigurable_keys_length == 0) {
888  // 15a. Return trapResult.
889  return AddKeysFromJSProxy(proxy, trap_result);
890  }
891  // 16. Let uncheckedResultKeys be a new List which is a copy of trapResult.
892  Zone set_zone(isolate_->allocator(), ZONE_NAME);
893  ZoneAllocationPolicy alloc(&set_zone);
894  const int kPresent = 1;
895  const int kGone = 0;
896  base::TemplateHashMapImpl<Handle<Name>, int, NameComparator,
897  ZoneAllocationPolicy>
898  unchecked_result_keys(ZoneHashMap::kDefaultHashMapCapacity,
899  NameComparator(isolate_), alloc);
900  int unchecked_result_keys_size = 0;
901  for (int i = 0; i < trap_result->length(); ++i) {
902  Handle<Name> key(Name::cast(trap_result->get(i)), isolate_);
903  auto entry = unchecked_result_keys.LookupOrInsert(key, key->Hash(), alloc);
904  if (entry->value != kPresent) {
905  entry->value = kPresent;
906  unchecked_result_keys_size++;
907  }
908  }
909  // 17. Repeat, for each key that is an element of targetNonconfigurableKeys:
910  for (int i = 0; i < nonconfigurable_keys_length; ++i) {
911  Object* raw_key = target_nonconfigurable_keys->get(i);
912  Handle<Name> key(Name::cast(raw_key), isolate_);
913  // 17a. If key is not an element of uncheckedResultKeys, throw a
914  // TypeError exception.
915  auto found = unchecked_result_keys.Lookup(key, key->Hash());
916  if (found == nullptr || found->value == kGone) {
917  isolate_->Throw(*isolate_->factory()->NewTypeError(
918  MessageTemplate::kProxyOwnKeysMissing, key));
919  return Nothing<bool>();
920  }
921  // 17b. Remove key from uncheckedResultKeys.
922  found->value = kGone;
923  unchecked_result_keys_size--;
924  }
925  // 18. If extensibleTarget is true, return trapResult.
926  if (extensible_target) {
927  return AddKeysFromJSProxy(proxy, trap_result);
928  }
929  // 19. Repeat, for each key that is an element of targetConfigurableKeys:
930  for (int i = 0; i < target_configurable_keys->length(); ++i) {
931  Object* raw_key = target_configurable_keys->get(i);
932  if (raw_key->IsSmi()) continue; // Zapped entry, was nonconfigurable.
933  Handle<Name> key(Name::cast(raw_key), isolate_);
934  // 19a. If key is not an element of uncheckedResultKeys, throw a
935  // TypeError exception.
936  auto found = unchecked_result_keys.Lookup(key, key->Hash());
937  if (found == nullptr || found->value == kGone) {
938  isolate_->Throw(*isolate_->factory()->NewTypeError(
939  MessageTemplate::kProxyOwnKeysMissing, key));
940  return Nothing<bool>();
941  }
942  // 19b. Remove key from uncheckedResultKeys.
943  found->value = kGone;
944  unchecked_result_keys_size--;
945  }
946  // 20. If uncheckedResultKeys is not empty, throw a TypeError exception.
947  if (unchecked_result_keys_size != 0) {
948  DCHECK_GT(unchecked_result_keys_size, 0);
949  isolate_->Throw(*isolate_->factory()->NewTypeError(
950  MessageTemplate::kProxyOwnKeysNonExtensible));
951  return Nothing<bool>();
952  }
953  // 21. Return trapResult.
954  return AddKeysFromJSProxy(proxy, trap_result);
955 }
956 
957 Maybe<bool> KeyAccumulator::CollectOwnJSProxyTargetKeys(
958  Handle<JSProxy> proxy, Handle<JSReceiver> target) {
959  // TODO(cbruni): avoid creating another KeyAccumulator
960  Handle<FixedArray> keys;
961  ASSIGN_RETURN_ON_EXCEPTION_VALUE(
962  isolate_, keys,
963  KeyAccumulator::GetKeys(
964  target, KeyCollectionMode::kOwnOnly, ALL_PROPERTIES,
965  GetKeysConversion::kConvertToString, is_for_in_, skip_indices_),
966  Nothing<bool>());
967  Maybe<bool> result = AddKeysFromJSProxy(proxy, keys);
968  return result;
969 }
970 
971 } // namespace internal
972 } // namespace v8
KeyCollectionMode
Definition: v8.h:3217
Definition: libplatform.h:13
PropertyFilter
Definition: v8.h:3185