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 KeyAccumulator::~KeyAccumulator() { 24 } 25 26 namespace { 27 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 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 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 64 Handle<OrderedHashSet> KeyAccumulator::keys() { 65 return Handle<OrderedHashSet>::cast(keys_); 66 } 67 68 void KeyAccumulator::AddKey(Object* key, AddKeyConversion convert) { 69 AddKey(handle(key, isolate_), convert); 70 } 71 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 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()->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> 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 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 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> 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 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 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. 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 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 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> 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> 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 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 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. 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 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: 791 explicit NameComparator(Isolate* isolate) : isolate_(isolate) {} 792 793 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. 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 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