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