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/objects/keys.h" 6 7#include "src/api/api-arguments-inl.h" 8#include "src/common/globals.h" 9#include "src/execution/isolate-inl.h" 10#include "src/handles/handles-inl.h" 11#include "src/heap/factory.h" 12#include "src/objects/api-callbacks.h" 13#include "src/objects/elements-inl.h" 14#include "src/objects/field-index-inl.h" 15#include "src/objects/hash-table-inl.h" 16#include "src/objects/module-inl.h" 17#include "src/objects/objects-inl.h" 18#include "src/objects/ordered-hash-table-inl.h" 19#include "src/objects/property-descriptor.h" 20#include "src/objects/prototype.h" 21#include "src/objects/slots-atomic-inl.h" 22#include "src/utils/identity-map.h" 23#include "src/zone/zone-hashmap.h" 24 25namespace v8 { 26namespace internal { 27 28#define RETURN_NOTHING_IF_NOT_SUCCESSFUL(call) \ 29 do { \ 30 if (!(call)) return Nothing<bool>(); \ 31 } while (false) 32 33#define RETURN_FAILURE_IF_NOT_SUCCESSFUL(call) \ 34 do { \ 35 ExceptionStatus status_enum_result = (call); \ 36 if (!status_enum_result) return status_enum_result; \ 37 } while (false) 38 39namespace { 40 41static bool ContainsOnlyValidKeys(Handle<FixedArray> array) { 42 int len = array->length(); 43 for (int i = 0; i < len; i++) { 44 Object e = array->get(i); 45 if (!(e.IsName() || e.IsNumber())) return false; 46 } 47 return true; 48} 49 50static int AddKey(Object key, Handle<FixedArray> combined_keys, 51 Handle<DescriptorArray> descs, int nof_descriptors, 52 int target) { 53 for (InternalIndex i : InternalIndex::Range(nof_descriptors)) { 54 if (descs->GetKey(i) == key) return 0; 55 } 56 combined_keys->set(target, key); 57 return 1; 58} 59 60static Handle<FixedArray> CombineKeys(Isolate* isolate, 61 Handle<FixedArray> own_keys, 62 Handle<FixedArray> prototype_chain_keys, 63 Handle<JSReceiver> receiver, 64 bool may_have_elements) { 65 int prototype_chain_keys_length = prototype_chain_keys->length(); 66 if (prototype_chain_keys_length == 0) return own_keys; 67 68 Map map = receiver->map(); 69 int nof_descriptors = map.NumberOfOwnDescriptors(); 70 if (nof_descriptors == 0 && !may_have_elements) return prototype_chain_keys; 71 72 Handle<DescriptorArray> descs(map.instance_descriptors(isolate), isolate); 73 int own_keys_length = own_keys.is_null() ? 0 : own_keys->length(); 74 Handle<FixedArray> combined_keys = isolate->factory()->NewFixedArray( 75 own_keys_length + prototype_chain_keys_length); 76 if (own_keys_length != 0) { 77 own_keys->CopyTo(0, *combined_keys, 0, own_keys_length); 78 } 79 int target_keys_length = own_keys_length; 80 for (int i = 0; i < prototype_chain_keys_length; i++) { 81 target_keys_length += AddKey(prototype_chain_keys->get(i), combined_keys, 82 descs, nof_descriptors, target_keys_length); 83 } 84 return FixedArray::ShrinkOrEmpty(isolate, combined_keys, target_keys_length); 85} 86 87} // namespace 88 89// static 90MaybeHandle<FixedArray> KeyAccumulator::GetKeys( 91 Handle<JSReceiver> object, KeyCollectionMode mode, PropertyFilter filter, 92 GetKeysConversion keys_conversion, bool is_for_in, bool skip_indices) { 93 Isolate* isolate = object->GetIsolate(); 94 FastKeyAccumulator accumulator(isolate, object, mode, filter, is_for_in, 95 skip_indices); 96 return accumulator.GetKeys(keys_conversion); 97} 98 99Handle<FixedArray> KeyAccumulator::GetKeys(GetKeysConversion convert) { 100 if (keys_.is_null()) { 101 return isolate_->factory()->empty_fixed_array(); 102 } 103 USE(ContainsOnlyValidKeys); 104 Handle<FixedArray> result = 105 OrderedHashSet::ConvertToKeysArray(isolate(), keys(), convert); 106 DCHECK(ContainsOnlyValidKeys(result)); 107 108 if (try_prototype_info_cache_ && !first_prototype_map_.is_null()) { 109 PrototypeInfo::cast(first_prototype_map_->prototype_info()) 110 .set_prototype_chain_enum_cache(*result); 111 Map::GetOrCreatePrototypeChainValidityCell( 112 Handle<Map>(receiver_->map(), isolate_), isolate_); 113 DCHECK(first_prototype_map_->IsPrototypeValidityCellValid()); 114 } 115 return result; 116} 117 118Handle<OrderedHashSet> KeyAccumulator::keys() { 119 return Handle<OrderedHashSet>::cast(keys_); 120} 121 122ExceptionStatus KeyAccumulator::AddKey(Object key, AddKeyConversion convert) { 123 return AddKey(handle(key, isolate_), convert); 124} 125 126ExceptionStatus KeyAccumulator::AddKey(Handle<Object> key, 127 AddKeyConversion convert) { 128 if (filter_ == PRIVATE_NAMES_ONLY) { 129 if (!key->IsSymbol()) return ExceptionStatus::kSuccess; 130 if (!Symbol::cast(*key).is_private_name()) return ExceptionStatus::kSuccess; 131 } else if (key->IsSymbol()) { 132 if (filter_ & SKIP_SYMBOLS) return ExceptionStatus::kSuccess; 133 if (Symbol::cast(*key).is_private()) return ExceptionStatus::kSuccess; 134 } else if (filter_ & SKIP_STRINGS) { 135 return ExceptionStatus::kSuccess; 136 } 137 138 if (IsShadowed(key)) return ExceptionStatus::kSuccess; 139 if (keys_.is_null()) { 140 keys_ = OrderedHashSet::Allocate(isolate_, 16).ToHandleChecked(); 141 } 142 uint32_t index; 143 if (convert == CONVERT_TO_ARRAY_INDEX && key->IsString() && 144 Handle<String>::cast(key)->AsArrayIndex(&index)) { 145 key = isolate_->factory()->NewNumberFromUint(index); 146 } 147 MaybeHandle<OrderedHashSet> new_set_candidate = 148 OrderedHashSet::Add(isolate(), keys(), key); 149 Handle<OrderedHashSet> new_set; 150 if (!new_set_candidate.ToHandle(&new_set)) { 151 THROW_NEW_ERROR_RETURN_VALUE( 152 isolate_, NewRangeError(MessageTemplate::kTooManyProperties), 153 ExceptionStatus::kException); 154 } 155 if (*new_set != *keys_) { 156 // The keys_ Set is converted directly to a FixedArray in GetKeys which can 157 // be left-trimmer. Hence the previous Set should not keep a pointer to the 158 // new one. 159 keys_->set(OrderedHashSet::NextTableIndex(), Smi::zero()); 160 keys_ = new_set; 161 } 162 return ExceptionStatus::kSuccess; 163} 164 165ExceptionStatus KeyAccumulator::AddKeys(Handle<FixedArray> array, 166 AddKeyConversion convert) { 167 int add_length = array->length(); 168 for (int i = 0; i < add_length; i++) { 169 Handle<Object> current(array->get(i), isolate_); 170 RETURN_FAILURE_IF_NOT_SUCCESSFUL(AddKey(current, convert)); 171 } 172 return ExceptionStatus::kSuccess; 173} 174 175ExceptionStatus KeyAccumulator::AddKeys(Handle<JSObject> array_like, 176 AddKeyConversion convert) { 177 DCHECK(array_like->IsJSArray() || array_like->HasSloppyArgumentsElements()); 178 ElementsAccessor* accessor = array_like->GetElementsAccessor(); 179 return accessor->AddElementsToKeyAccumulator(array_like, this, convert); 180} 181 182MaybeHandle<FixedArray> FilterProxyKeys(KeyAccumulator* accumulator, 183 Handle<JSProxy> owner, 184 Handle<FixedArray> keys, 185 PropertyFilter filter, 186 bool skip_indices) { 187 if (filter == ALL_PROPERTIES) { 188 // Nothing to do. 189 return keys; 190 } 191 Isolate* isolate = accumulator->isolate(); 192 int store_position = 0; 193 for (int i = 0; i < keys->length(); ++i) { 194 Handle<Name> key(Name::cast(keys->get(i)), isolate); 195 if (key->FilterKey(filter)) continue; // Skip this key. 196 if (skip_indices) { 197 uint32_t index; 198 if (key->AsArrayIndex(&index)) continue; // Skip this key. 199 } 200 if (filter & ONLY_ENUMERABLE) { 201 PropertyDescriptor desc; 202 Maybe<bool> found = 203 JSProxy::GetOwnPropertyDescriptor(isolate, owner, key, &desc); 204 MAYBE_RETURN(found, MaybeHandle<FixedArray>()); 205 if (!found.FromJust()) continue; 206 if (!desc.enumerable()) { 207 accumulator->AddShadowingKey(key); 208 continue; 209 } 210 } 211 // Keep this key. 212 if (store_position != i) { 213 keys->set(store_position, *key); 214 } 215 store_position++; 216 } 217 return FixedArray::ShrinkOrEmpty(isolate, keys, store_position); 218} 219 220// Returns "nothing" in case of exception, "true" on success. 221Maybe<bool> KeyAccumulator::AddKeysFromJSProxy(Handle<JSProxy> proxy, 222 Handle<FixedArray> keys) { 223 // Postpone the enumerable check for for-in to the ForInFilter step. 224 if (!is_for_in_) { 225 ASSIGN_RETURN_ON_EXCEPTION_VALUE( 226 isolate_, keys, 227 FilterProxyKeys(this, proxy, keys, filter_, skip_indices_), 228 Nothing<bool>()); 229 } 230 // https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeys 231 // As of 10.5.11.9 says, the keys collected from Proxy should not contain 232 // any duplicates. And the order of the keys is preserved by the 233 // OrderedHashTable. 234 RETURN_NOTHING_IF_NOT_SUCCESSFUL(AddKeys(keys, CONVERT_TO_ARRAY_INDEX)); 235 return Just(true); 236} 237 238Maybe<bool> KeyAccumulator::CollectKeys(Handle<JSReceiver> receiver, 239 Handle<JSReceiver> object) { 240 // Proxies have no hidden prototype and we should not trigger the 241 // [[GetPrototypeOf]] trap on the last iteration when using 242 // AdvanceFollowingProxies. 243 if (mode_ == KeyCollectionMode::kOwnOnly && object->IsJSProxy()) { 244 MAYBE_RETURN(CollectOwnJSProxyKeys(receiver, Handle<JSProxy>::cast(object)), 245 Nothing<bool>()); 246 return Just(true); 247 } 248 249 PrototypeIterator::WhereToEnd end = mode_ == KeyCollectionMode::kOwnOnly 250 ? PrototypeIterator::END_AT_NON_HIDDEN 251 : PrototypeIterator::END_AT_NULL; 252 for (PrototypeIterator iter(isolate_, object, kStartAtReceiver, end); 253 !iter.IsAtEnd();) { 254 // Start the shadow checks only after the first prototype has added 255 // shadowing keys. 256 if (HasShadowingKeys()) skip_shadow_check_ = false; 257 Handle<JSReceiver> current = 258 PrototypeIterator::GetCurrent<JSReceiver>(iter); 259 Maybe<bool> result = Just(false); // Dummy initialization. 260 if (current->IsJSProxy()) { 261 result = CollectOwnJSProxyKeys(receiver, Handle<JSProxy>::cast(current)); 262 } else { 263 DCHECK(current->IsJSObject()); 264 result = CollectOwnKeys(receiver, Handle<JSObject>::cast(current)); 265 } 266 MAYBE_RETURN(result, Nothing<bool>()); 267 if (!result.FromJust()) break; // |false| means "stop iterating". 268 // Iterate through proxies but ignore access checks for the ALL_CAN_READ 269 // case on API objects for OWN_ONLY keys handled in CollectOwnKeys. 270 if (!iter.AdvanceFollowingProxiesIgnoringAccessChecks()) { 271 return Nothing<bool>(); 272 } 273 if (!last_non_empty_prototype_.is_null() && 274 *last_non_empty_prototype_ == *current) { 275 break; 276 } 277 } 278 return Just(true); 279} 280 281bool KeyAccumulator::HasShadowingKeys() { return !shadowing_keys_.is_null(); } 282 283bool KeyAccumulator::IsShadowed(Handle<Object> key) { 284 if (!HasShadowingKeys() || skip_shadow_check_) return false; 285 return shadowing_keys_->Has(isolate_, key); 286} 287 288void KeyAccumulator::AddShadowingKey(Object key, 289 AllowGarbageCollection* allow_gc) { 290 if (mode_ == KeyCollectionMode::kOwnOnly) return; 291 AddShadowingKey(handle(key, isolate_)); 292} 293void KeyAccumulator::AddShadowingKey(Handle<Object> key) { 294 if (mode_ == KeyCollectionMode::kOwnOnly) return; 295 if (shadowing_keys_.is_null()) { 296 shadowing_keys_ = ObjectHashSet::New(isolate_, 16); 297 } 298 shadowing_keys_ = ObjectHashSet::Add(isolate(), shadowing_keys_, key); 299} 300 301namespace { 302 303void TrySettingEmptyEnumCache(JSReceiver object) { 304 Map map = object.map(); 305 DCHECK_EQ(kInvalidEnumCacheSentinel, map.EnumLength()); 306 if (!map.OnlyHasSimpleProperties()) return; 307 if (map.IsJSProxyMap()) return; 308 if (map.NumberOfEnumerableProperties() > 0) return; 309 DCHECK(object.IsJSObject()); 310 map.SetEnumLength(0); 311} 312 313bool CheckAndInitalizeEmptyEnumCache(JSReceiver object) { 314 if (object.map().EnumLength() == kInvalidEnumCacheSentinel) { 315 TrySettingEmptyEnumCache(object); 316 } 317 if (object.map().EnumLength() != 0) return false; 318 DCHECK(object.IsJSObject()); 319 return !JSObject::cast(object).HasEnumerableElements(); 320} 321} // namespace 322 323void FastKeyAccumulator::Prepare() { 324 DisallowGarbageCollection no_gc; 325 // Directly go for the fast path for OWN_ONLY keys. 326 if (mode_ == KeyCollectionMode::kOwnOnly) return; 327 // Fully walk the prototype chain and find the last prototype with keys. 328 is_receiver_simple_enum_ = false; 329 has_empty_prototype_ = true; 330 only_own_has_simple_elements_ = 331 !receiver_->map().IsCustomElementsReceiverMap(); 332 JSReceiver last_prototype; 333 may_have_elements_ = MayHaveElements(*receiver_); 334 for (PrototypeIterator iter(isolate_, *receiver_); !iter.IsAtEnd(); 335 iter.Advance()) { 336 JSReceiver current = iter.GetCurrent<JSReceiver>(); 337 if (!may_have_elements_ || only_own_has_simple_elements_) { 338 if (MayHaveElements(current)) { 339 may_have_elements_ = true; 340 only_own_has_simple_elements_ = false; 341 } 342 } 343 bool has_no_properties = CheckAndInitalizeEmptyEnumCache(current); 344 if (has_no_properties) continue; 345 last_prototype = current; 346 has_empty_prototype_ = false; 347 } 348 // Check if we should try to create/use prototype info cache. 349 try_prototype_info_cache_ = TryPrototypeInfoCache(receiver_); 350 if (has_prototype_info_cache_) return; 351 if (has_empty_prototype_) { 352 is_receiver_simple_enum_ = 353 receiver_->map().EnumLength() != kInvalidEnumCacheSentinel && 354 !JSObject::cast(*receiver_).HasEnumerableElements(); 355 } else if (!last_prototype.is_null()) { 356 last_non_empty_prototype_ = handle(last_prototype, isolate_); 357 } 358} 359 360namespace { 361 362Handle<FixedArray> ReduceFixedArrayTo(Isolate* isolate, 363 Handle<FixedArray> array, int length) { 364 DCHECK_LE(length, array->length()); 365 if (array->length() == length) return array; 366 return isolate->factory()->CopyFixedArrayUpTo(array, length); 367} 368 369// Initializes and directly returns the enume cache. Users of this function 370// have to make sure to never directly leak the enum cache. 371Handle<FixedArray> GetFastEnumPropertyKeys(Isolate* isolate, 372 Handle<JSObject> object) { 373 Handle<Map> map(object->map(), isolate); 374 Handle<FixedArray> keys( 375 map->instance_descriptors(isolate).enum_cache().keys(), isolate); 376 377 // Check if the {map} has a valid enum length, which implies that it 378 // must have a valid enum cache as well. 379 int enum_length = map->EnumLength(); 380 if (enum_length != kInvalidEnumCacheSentinel) { 381 DCHECK(map->OnlyHasSimpleProperties()); 382 DCHECK_LE(enum_length, keys->length()); 383 DCHECK_EQ(enum_length, map->NumberOfEnumerableProperties()); 384 isolate->counters()->enum_cache_hits()->Increment(); 385 return ReduceFixedArrayTo(isolate, keys, enum_length); 386 } 387 388 // Determine the actual number of enumerable properties of the {map}. 389 enum_length = map->NumberOfEnumerableProperties(); 390 391 // Check if there's already a shared enum cache on the {map}s 392 // DescriptorArray with sufficient number of entries. 393 if (enum_length <= keys->length()) { 394 if (map->OnlyHasSimpleProperties()) map->SetEnumLength(enum_length); 395 isolate->counters()->enum_cache_hits()->Increment(); 396 return ReduceFixedArrayTo(isolate, keys, enum_length); 397 } 398 399 Handle<DescriptorArray> descriptors = 400 Handle<DescriptorArray>(map->instance_descriptors(isolate), isolate); 401 isolate->counters()->enum_cache_misses()->Increment(); 402 403 // Create the keys array. 404 int index = 0; 405 bool fields_only = true; 406 keys = isolate->factory()->NewFixedArray(enum_length); 407 for (InternalIndex i : map->IterateOwnDescriptors()) { 408 DisallowGarbageCollection no_gc; 409 PropertyDetails details = descriptors->GetDetails(i); 410 if (details.IsDontEnum()) continue; 411 Object key = descriptors->GetKey(i); 412 if (key.IsSymbol()) continue; 413 keys->set(index, key); 414 if (details.location() != PropertyLocation::kField) fields_only = false; 415 index++; 416 } 417 DCHECK_EQ(index, keys->length()); 418 419 // Optionally also create the indices array. 420 Handle<FixedArray> indices = isolate->factory()->empty_fixed_array(); 421 if (fields_only) { 422 indices = isolate->factory()->NewFixedArray(enum_length); 423 index = 0; 424 for (InternalIndex i : map->IterateOwnDescriptors()) { 425 DisallowGarbageCollection no_gc; 426 PropertyDetails details = descriptors->GetDetails(i); 427 if (details.IsDontEnum()) continue; 428 Object key = descriptors->GetKey(i); 429 if (key.IsSymbol()) continue; 430 DCHECK_EQ(PropertyKind::kData, details.kind()); 431 DCHECK_EQ(PropertyLocation::kField, details.location()); 432 FieldIndex field_index = FieldIndex::ForDescriptor(*map, i); 433 indices->set(index, Smi::FromInt(field_index.GetLoadByFieldIndex())); 434 index++; 435 } 436 DCHECK_EQ(index, indices->length()); 437 } 438 439 DescriptorArray::InitializeOrChangeEnumCache(descriptors, isolate, keys, 440 indices); 441 if (map->OnlyHasSimpleProperties()) map->SetEnumLength(enum_length); 442 443 return keys; 444} 445 446template <bool fast_properties> 447MaybeHandle<FixedArray> GetOwnKeysWithElements(Isolate* isolate, 448 Handle<JSObject> object, 449 GetKeysConversion convert, 450 bool skip_indices) { 451 Handle<FixedArray> keys; 452 ElementsAccessor* accessor = object->GetElementsAccessor(); 453 if (fast_properties) { 454 keys = GetFastEnumPropertyKeys(isolate, object); 455 } else { 456 // TODO(cbruni): preallocate big enough array to also hold elements. 457 keys = KeyAccumulator::GetOwnEnumPropertyKeys(isolate, object); 458 } 459 460 MaybeHandle<FixedArray> result; 461 if (skip_indices) { 462 result = keys; 463 } else { 464 result = 465 accessor->PrependElementIndices(object, keys, convert, ONLY_ENUMERABLE); 466 } 467 468 if (FLAG_trace_for_in_enumerate) { 469 PrintF("| strings=%d symbols=0 elements=%u || prototypes>=1 ||\n", 470 keys->length(), result.ToHandleChecked()->length() - keys->length()); 471 } 472 return result; 473} 474 475} // namespace 476 477MaybeHandle<FixedArray> FastKeyAccumulator::GetKeys( 478 GetKeysConversion keys_conversion) { 479 // TODO(v8:9401): We should extend the fast path of KeyAccumulator::GetKeys to 480 // also use fast path even when filter = SKIP_SYMBOLS. We used to pass wrong 481 // filter to use fast path in cases where we tried to verify all properties 482 // are enumerable. However these checks weren't correct and passing the wrong 483 // filter led to wrong behaviour. 484 if (filter_ == ENUMERABLE_STRINGS) { 485 Handle<FixedArray> keys; 486 if (GetKeysFast(keys_conversion).ToHandle(&keys)) { 487 return keys; 488 } 489 if (isolate_->has_pending_exception()) return MaybeHandle<FixedArray>(); 490 } 491 492 if (try_prototype_info_cache_) { 493 return GetKeysWithPrototypeInfoCache(keys_conversion); 494 } 495 return GetKeysSlow(keys_conversion); 496} 497 498MaybeHandle<FixedArray> FastKeyAccumulator::GetKeysFast( 499 GetKeysConversion keys_conversion) { 500 bool own_only = has_empty_prototype_ || mode_ == KeyCollectionMode::kOwnOnly; 501 Map map = receiver_->map(); 502 if (!own_only || map.IsCustomElementsReceiverMap()) { 503 return MaybeHandle<FixedArray>(); 504 } 505 506 // From this point on we are certain to only collect own keys. 507 DCHECK(receiver_->IsJSObject()); 508 Handle<JSObject> object = Handle<JSObject>::cast(receiver_); 509 510 // Do not try to use the enum-cache for dict-mode objects. 511 if (map.is_dictionary_map()) { 512 return GetOwnKeysWithElements<false>(isolate_, object, keys_conversion, 513 skip_indices_); 514 } 515 int enum_length = receiver_->map().EnumLength(); 516 if (enum_length == kInvalidEnumCacheSentinel) { 517 Handle<FixedArray> keys; 518 // Try initializing the enum cache and return own properties. 519 if (GetOwnKeysWithUninitializedEnumCache().ToHandle(&keys)) { 520 if (FLAG_trace_for_in_enumerate) { 521 PrintF("| strings=%d symbols=0 elements=0 || prototypes>=1 ||\n", 522 keys->length()); 523 } 524 is_receiver_simple_enum_ = 525 object->map().EnumLength() != kInvalidEnumCacheSentinel; 526 return keys; 527 } 528 } 529 // The properties-only case failed because there were probably elements on the 530 // receiver. 531 return GetOwnKeysWithElements<true>(isolate_, object, keys_conversion, 532 skip_indices_); 533} 534 535MaybeHandle<FixedArray> 536FastKeyAccumulator::GetOwnKeysWithUninitializedEnumCache() { 537 Handle<JSObject> object = Handle<JSObject>::cast(receiver_); 538 // Uninitalized enum cache 539 Map map = object->map(); 540 if (object->elements() != ReadOnlyRoots(isolate_).empty_fixed_array() && 541 object->elements() != 542 ReadOnlyRoots(isolate_).empty_slow_element_dictionary()) { 543 // Assume that there are elements. 544 return MaybeHandle<FixedArray>(); 545 } 546 int number_of_own_descriptors = map.NumberOfOwnDescriptors(); 547 if (number_of_own_descriptors == 0) { 548 map.SetEnumLength(0); 549 return isolate_->factory()->empty_fixed_array(); 550 } 551 // We have no elements but possibly enumerable property keys, hence we can 552 // directly initialize the enum cache. 553 Handle<FixedArray> keys = GetFastEnumPropertyKeys(isolate_, object); 554 if (is_for_in_) return keys; 555 // Do not leak the enum cache as it might end up as an elements backing store. 556 return isolate_->factory()->CopyFixedArray(keys); 557} 558 559MaybeHandle<FixedArray> FastKeyAccumulator::GetKeysSlow( 560 GetKeysConversion keys_conversion) { 561 KeyAccumulator accumulator(isolate_, mode_, filter_); 562 accumulator.set_is_for_in(is_for_in_); 563 accumulator.set_skip_indices(skip_indices_); 564 accumulator.set_last_non_empty_prototype(last_non_empty_prototype_); 565 accumulator.set_may_have_elements(may_have_elements_); 566 accumulator.set_first_prototype_map(first_prototype_map_); 567 accumulator.set_try_prototype_info_cache(try_prototype_info_cache_); 568 569 MAYBE_RETURN(accumulator.CollectKeys(receiver_, receiver_), 570 MaybeHandle<FixedArray>()); 571 return accumulator.GetKeys(keys_conversion); 572} 573 574MaybeHandle<FixedArray> FastKeyAccumulator::GetKeysWithPrototypeInfoCache( 575 GetKeysConversion keys_conversion) { 576 Handle<FixedArray> own_keys; 577 if (may_have_elements_) { 578 MaybeHandle<FixedArray> maybe_own_keys; 579 if (receiver_->map().is_dictionary_map()) { 580 maybe_own_keys = GetOwnKeysWithElements<false>( 581 isolate_, Handle<JSObject>::cast(receiver_), keys_conversion, 582 skip_indices_); 583 } else { 584 maybe_own_keys = GetOwnKeysWithElements<true>( 585 isolate_, Handle<JSObject>::cast(receiver_), keys_conversion, 586 skip_indices_); 587 } 588 ASSIGN_RETURN_ON_EXCEPTION(isolate_, own_keys, maybe_own_keys, FixedArray); 589 } else { 590 own_keys = KeyAccumulator::GetOwnEnumPropertyKeys( 591 isolate_, Handle<JSObject>::cast(receiver_)); 592 } 593 Handle<FixedArray> prototype_chain_keys; 594 if (has_prototype_info_cache_) { 595 prototype_chain_keys = 596 handle(FixedArray::cast( 597 PrototypeInfo::cast(first_prototype_map_->prototype_info()) 598 .prototype_chain_enum_cache()), 599 isolate_); 600 } else { 601 KeyAccumulator accumulator(isolate_, mode_, filter_); 602 accumulator.set_is_for_in(is_for_in_); 603 accumulator.set_skip_indices(skip_indices_); 604 accumulator.set_last_non_empty_prototype(last_non_empty_prototype_); 605 accumulator.set_may_have_elements(may_have_elements_); 606 accumulator.set_receiver(receiver_); 607 accumulator.set_first_prototype_map(first_prototype_map_); 608 accumulator.set_try_prototype_info_cache(try_prototype_info_cache_); 609 MAYBE_RETURN(accumulator.CollectKeys(first_prototype_, first_prototype_), 610 MaybeHandle<FixedArray>()); 611 prototype_chain_keys = accumulator.GetKeys(keys_conversion); 612 } 613 Handle<FixedArray> result = CombineKeys( 614 isolate_, own_keys, prototype_chain_keys, receiver_, may_have_elements_); 615 if (is_for_in_ && own_keys.is_identical_to(result)) { 616 // Don't leak the enumeration cache without the receiver since it might get 617 // trimmed otherwise. 618 return isolate_->factory()->CopyFixedArrayUpTo(result, result->length()); 619 } 620 return result; 621} 622 623bool FastKeyAccumulator::MayHaveElements(JSReceiver receiver) { 624 if (!receiver.IsJSObject()) return true; 625 JSObject object = JSObject::cast(receiver); 626 if (object.HasEnumerableElements()) return true; 627 if (object.HasIndexedInterceptor()) return true; 628 return false; 629} 630 631bool FastKeyAccumulator::TryPrototypeInfoCache(Handle<JSReceiver> receiver) { 632 if (may_have_elements_ && !only_own_has_simple_elements_) return false; 633 Handle<JSObject> object = Handle<JSObject>::cast(receiver); 634 if (!object->HasFastProperties()) return false; 635 if (object->HasNamedInterceptor()) return false; 636 if (object->IsAccessCheckNeeded() && 637 !isolate_->MayAccess(handle(isolate_->context(), isolate_), object)) { 638 return false; 639 } 640 HeapObject prototype = receiver->map().prototype(); 641 if (prototype.is_null()) return false; 642 if (!prototype.map().is_prototype_map() || 643 !prototype.map().prototype_info().IsPrototypeInfo()) { 644 return false; 645 } 646 first_prototype_ = handle(JSReceiver::cast(prototype), isolate_); 647 Handle<Map> map(prototype.map(), isolate_); 648 first_prototype_map_ = map; 649 has_prototype_info_cache_ = map->IsPrototypeValidityCellValid() && 650 PrototypeInfo::cast(map->prototype_info()) 651 .prototype_chain_enum_cache() 652 .IsFixedArray(); 653 return true; 654} 655 656V8_WARN_UNUSED_RESULT ExceptionStatus 657KeyAccumulator::FilterForEnumerableProperties( 658 Handle<JSReceiver> receiver, Handle<JSObject> object, 659 Handle<InterceptorInfo> interceptor, Handle<JSObject> result, 660 IndexedOrNamed type) { 661 DCHECK(result->IsJSArray() || result->HasSloppyArgumentsElements()); 662 ElementsAccessor* accessor = result->GetElementsAccessor(); 663 664 size_t length = accessor->GetCapacity(*result, result->elements()); 665 for (InternalIndex entry : InternalIndex::Range(length)) { 666 if (!accessor->HasEntry(*result, entry)) continue; 667 668 // args are invalid after args.Call(), create a new one in every iteration. 669 PropertyCallbackArguments args(isolate_, interceptor->data(), *receiver, 670 *object, Just(kDontThrow)); 671 672 Handle<Object> element = accessor->Get(result, entry); 673 Handle<Object> attributes; 674 if (type == kIndexed) { 675 uint32_t number; 676 CHECK(element->ToUint32(&number)); 677 attributes = args.CallIndexedQuery(interceptor, number); 678 } else { 679 CHECK(element->IsName()); 680 attributes = 681 args.CallNamedQuery(interceptor, Handle<Name>::cast(element)); 682 } 683 684 if (!attributes.is_null()) { 685 int32_t value; 686 CHECK(attributes->ToInt32(&value)); 687 if ((value & DONT_ENUM) == 0) { 688 RETURN_FAILURE_IF_NOT_SUCCESSFUL(AddKey(element, DO_NOT_CONVERT)); 689 } 690 } 691 } 692 return ExceptionStatus::kSuccess; 693} 694 695// Returns |true| on success, |nothing| on exception. 696Maybe<bool> KeyAccumulator::CollectInterceptorKeysInternal( 697 Handle<JSReceiver> receiver, Handle<JSObject> object, 698 Handle<InterceptorInfo> interceptor, IndexedOrNamed type) { 699 PropertyCallbackArguments enum_args(isolate_, interceptor->data(), *receiver, 700 *object, Just(kDontThrow)); 701 702 Handle<JSObject> result; 703 if (!interceptor->enumerator().IsUndefined(isolate_)) { 704 if (type == kIndexed) { 705 result = enum_args.CallIndexedEnumerator(interceptor); 706 } else { 707 DCHECK_EQ(type, kNamed); 708 result = enum_args.CallNamedEnumerator(interceptor); 709 } 710 } 711 RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate_, Nothing<bool>()); 712 if (result.is_null()) return Just(true); 713 714 if ((filter_ & ONLY_ENUMERABLE) && 715 !interceptor->query().IsUndefined(isolate_)) { 716 RETURN_NOTHING_IF_NOT_SUCCESSFUL(FilterForEnumerableProperties( 717 receiver, object, interceptor, result, type)); 718 } else { 719 RETURN_NOTHING_IF_NOT_SUCCESSFUL(AddKeys( 720 result, type == kIndexed ? CONVERT_TO_ARRAY_INDEX : DO_NOT_CONVERT)); 721 } 722 return Just(true); 723} 724 725Maybe<bool> KeyAccumulator::CollectInterceptorKeys(Handle<JSReceiver> receiver, 726 Handle<JSObject> object, 727 IndexedOrNamed type) { 728 if (type == kIndexed) { 729 if (!object->HasIndexedInterceptor()) return Just(true); 730 } else { 731 if (!object->HasNamedInterceptor()) return Just(true); 732 } 733 Handle<InterceptorInfo> interceptor(type == kIndexed 734 ? object->GetIndexedInterceptor() 735 : object->GetNamedInterceptor(), 736 isolate_); 737 if ((filter() & ONLY_ALL_CAN_READ) && !interceptor->all_can_read()) { 738 return Just(true); 739 } 740 return CollectInterceptorKeysInternal(receiver, object, interceptor, type); 741} 742 743Maybe<bool> KeyAccumulator::CollectOwnElementIndices( 744 Handle<JSReceiver> receiver, Handle<JSObject> object) { 745 if (filter_ & SKIP_STRINGS || skip_indices_) return Just(true); 746 747 ElementsAccessor* accessor = object->GetElementsAccessor(); 748 RETURN_NOTHING_IF_NOT_SUCCESSFUL( 749 accessor->CollectElementIndices(object, this)); 750 return CollectInterceptorKeys(receiver, object, kIndexed); 751} 752 753namespace { 754 755template <bool skip_symbols> 756base::Optional<int> CollectOwnPropertyNamesInternal( 757 Handle<JSObject> object, KeyAccumulator* keys, 758 Handle<DescriptorArray> descs, int start_index, int limit) { 759 AllowGarbageCollection allow_gc; 760 int first_skipped = -1; 761 PropertyFilter filter = keys->filter(); 762 KeyCollectionMode mode = keys->mode(); 763 for (InternalIndex i : InternalIndex::Range(start_index, limit)) { 764 bool is_shadowing_key = false; 765 PropertyDetails details = descs->GetDetails(i); 766 767 if ((details.attributes() & filter) != 0) { 768 if (mode == KeyCollectionMode::kIncludePrototypes) { 769 is_shadowing_key = true; 770 } else { 771 continue; 772 } 773 } 774 775 if (filter & ONLY_ALL_CAN_READ) { 776 if (details.kind() != PropertyKind::kAccessor) continue; 777 Object accessors = descs->GetStrongValue(i); 778 if (!accessors.IsAccessorInfo()) continue; 779 if (!AccessorInfo::cast(accessors).all_can_read()) continue; 780 } 781 782 Name key = descs->GetKey(i); 783 if (skip_symbols == key.IsSymbol()) { 784 if (first_skipped == -1) first_skipped = i.as_int(); 785 continue; 786 } 787 if (key.FilterKey(keys->filter())) continue; 788 789 if (is_shadowing_key) { 790 // This might allocate, but {key} is not used afterwards. 791 keys->AddShadowingKey(key, &allow_gc); 792 continue; 793 } else { 794 if (keys->AddKey(key, DO_NOT_CONVERT) != ExceptionStatus::kSuccess) { 795 return base::Optional<int>(); 796 } 797 } 798 } 799 return first_skipped; 800} 801 802// Logic shared between different specializations of CopyEnumKeysTo. 803template <typename Dictionary> 804void CommonCopyEnumKeysTo(Isolate* isolate, Handle<Dictionary> dictionary, 805 Handle<FixedArray> storage, KeyCollectionMode mode, 806 KeyAccumulator* accumulator) { 807 DCHECK_IMPLIES(mode != KeyCollectionMode::kOwnOnly, accumulator != nullptr); 808 int length = storage->length(); 809 int properties = 0; 810 ReadOnlyRoots roots(isolate); 811 812 AllowGarbageCollection allow_gc; 813 for (InternalIndex i : dictionary->IterateEntries()) { 814 Object key; 815 if (!dictionary->ToKey(roots, i, &key)) continue; 816 bool is_shadowing_key = false; 817 if (key.IsSymbol()) continue; 818 PropertyDetails details = dictionary->DetailsAt(i); 819 if (details.IsDontEnum()) { 820 if (mode == KeyCollectionMode::kIncludePrototypes) { 821 is_shadowing_key = true; 822 } else { 823 continue; 824 } 825 } 826 if (is_shadowing_key) { 827 // This might allocate, but {key} is not used afterwards. 828 accumulator->AddShadowingKey(key, &allow_gc); 829 continue; 830 } else { 831 if (Dictionary::kIsOrderedDictionaryType) { 832 storage->set(properties, Name::cast(key)); 833 } else { 834 // If the dictionary does not store elements in enumeration order, 835 // we need to sort it afterwards in CopyEnumKeysTo. To enable this we 836 // need to store indices at this point, rather than the values at the 837 // given indices. 838 storage->set(properties, Smi::FromInt(i.as_int())); 839 } 840 } 841 properties++; 842 if (mode == KeyCollectionMode::kOwnOnly && properties == length) break; 843 } 844 845 CHECK_EQ(length, properties); 846} 847 848// Copies enumerable keys to preallocated fixed array. 849// Does not throw for uninitialized exports in module namespace objects, so 850// this has to be checked separately. 851template <typename Dictionary> 852void CopyEnumKeysTo(Isolate* isolate, Handle<Dictionary> dictionary, 853 Handle<FixedArray> storage, KeyCollectionMode mode, 854 KeyAccumulator* accumulator) { 855 STATIC_ASSERT(!Dictionary::kIsOrderedDictionaryType); 856 857 CommonCopyEnumKeysTo<Dictionary>(isolate, dictionary, storage, mode, 858 accumulator); 859 860 int length = storage->length(); 861 862 DisallowGarbageCollection no_gc; 863 Dictionary raw_dictionary = *dictionary; 864 FixedArray raw_storage = *storage; 865 EnumIndexComparator<Dictionary> cmp(raw_dictionary); 866 // Use AtomicSlot wrapper to ensure that std::sort uses atomic load and 867 // store operations that are safe for concurrent marking. 868 AtomicSlot start(storage->GetFirstElementAddress()); 869 std::sort(start, start + length, cmp); 870 for (int i = 0; i < length; i++) { 871 InternalIndex index(Smi::ToInt(raw_storage.get(i))); 872 raw_storage.set(i, raw_dictionary.NameAt(index)); 873 } 874} 875 876template <> 877void CopyEnumKeysTo(Isolate* isolate, Handle<SwissNameDictionary> dictionary, 878 Handle<FixedArray> storage, KeyCollectionMode mode, 879 KeyAccumulator* accumulator) { 880 CommonCopyEnumKeysTo<SwissNameDictionary>(isolate, dictionary, storage, mode, 881 accumulator); 882 883 // No need to sort, as CommonCopyEnumKeysTo on OrderedNameDictionary 884 // adds entries to |storage| in the dict's insertion order 885 // Further, the template argument true above means that |storage| 886 // now contains the actual values from |dictionary|, rather than indices. 887} 888 889template <class T> 890Handle<FixedArray> GetOwnEnumPropertyDictionaryKeys(Isolate* isolate, 891 KeyCollectionMode mode, 892 KeyAccumulator* accumulator, 893 Handle<JSObject> object, 894 T raw_dictionary) { 895 Handle<T> dictionary(raw_dictionary, isolate); 896 if (dictionary->NumberOfElements() == 0) { 897 return isolate->factory()->empty_fixed_array(); 898 } 899 int length = dictionary->NumberOfEnumerableProperties(); 900 Handle<FixedArray> storage = isolate->factory()->NewFixedArray(length); 901 CopyEnumKeysTo(isolate, dictionary, storage, mode, accumulator); 902 return storage; 903} 904 905// Collect the keys from |dictionary| into |keys|, in ascending chronological 906// order of property creation. 907template <typename Dictionary> 908ExceptionStatus CollectKeysFromDictionary(Handle<Dictionary> dictionary, 909 KeyAccumulator* keys) { 910 Isolate* isolate = keys->isolate(); 911 ReadOnlyRoots roots(isolate); 912 // TODO(jkummerow): Consider using a std::unique_ptr<InternalIndex[]> instead. 913 Handle<FixedArray> array = 914 isolate->factory()->NewFixedArray(dictionary->NumberOfElements()); 915 int array_size = 0; 916 PropertyFilter filter = keys->filter(); 917 // Handle enumerable strings in CopyEnumKeysTo. 918 DCHECK_NE(keys->filter(), ENUMERABLE_STRINGS); 919 { 920 DisallowGarbageCollection no_gc; 921 for (InternalIndex i : dictionary->IterateEntries()) { 922 Object key; 923 Dictionary raw_dictionary = *dictionary; 924 if (!raw_dictionary.ToKey(roots, i, &key)) continue; 925 if (key.FilterKey(filter)) continue; 926 PropertyDetails details = raw_dictionary.DetailsAt(i); 927 if ((details.attributes() & filter) != 0) { 928 AllowGarbageCollection gc; 929 // This might allocate, but {key} is not used afterwards. 930 keys->AddShadowingKey(key, &gc); 931 continue; 932 } 933 if (filter & ONLY_ALL_CAN_READ) { 934 if (details.kind() != PropertyKind::kAccessor) continue; 935 Object accessors = raw_dictionary.ValueAt(i); 936 if (!accessors.IsAccessorInfo()) continue; 937 if (!AccessorInfo::cast(accessors).all_can_read()) continue; 938 } 939 // TODO(emrich): consider storing keys instead of indices into the array 940 // in case of ordered dictionary type. 941 array->set(array_size++, Smi::FromInt(i.as_int())); 942 } 943 if (!Dictionary::kIsOrderedDictionaryType) { 944 // Sorting only needed if it's an unordered dictionary, 945 // otherwise we traversed elements in insertion order 946 947 EnumIndexComparator<Dictionary> cmp(*dictionary); 948 // Use AtomicSlot wrapper to ensure that std::sort uses atomic load and 949 // store operations that are safe for concurrent marking. 950 AtomicSlot start(array->GetFirstElementAddress()); 951 std::sort(start, start + array_size, cmp); 952 } 953 } 954 955 bool has_seen_symbol = false; 956 for (int i = 0; i < array_size; i++) { 957 InternalIndex index(Smi::ToInt(array->get(i))); 958 Object key = dictionary->NameAt(index); 959 if (key.IsSymbol()) { 960 has_seen_symbol = true; 961 continue; 962 } 963 ExceptionStatus status = keys->AddKey(key, DO_NOT_CONVERT); 964 if (!status) return status; 965 } 966 if (has_seen_symbol) { 967 for (int i = 0; i < array_size; i++) { 968 InternalIndex index(Smi::ToInt(array->get(i))); 969 Object key = dictionary->NameAt(index); 970 if (!key.IsSymbol()) continue; 971 ExceptionStatus status = keys->AddKey(key, DO_NOT_CONVERT); 972 if (!status) return status; 973 } 974 } 975 return ExceptionStatus::kSuccess; 976} 977 978} // namespace 979 980Maybe<bool> KeyAccumulator::CollectOwnPropertyNames(Handle<JSReceiver> receiver, 981 Handle<JSObject> object) { 982 if (filter_ == ENUMERABLE_STRINGS) { 983 Handle<FixedArray> enum_keys; 984 if (object->HasFastProperties()) { 985 enum_keys = KeyAccumulator::GetOwnEnumPropertyKeys(isolate_, object); 986 // If the number of properties equals the length of enumerable properties 987 // we do not have to filter out non-enumerable ones 988 Map map = object->map(); 989 int nof_descriptors = map.NumberOfOwnDescriptors(); 990 if (enum_keys->length() != nof_descriptors) { 991 if (map.prototype(isolate_) != ReadOnlyRoots(isolate_).null_value()) { 992 AllowGarbageCollection allow_gc; 993 Handle<DescriptorArray> descs = Handle<DescriptorArray>( 994 map.instance_descriptors(isolate_), isolate_); 995 for (InternalIndex i : InternalIndex::Range(nof_descriptors)) { 996 PropertyDetails details = descs->GetDetails(i); 997 if (!details.IsDontEnum()) continue; 998 this->AddShadowingKey(descs->GetKey(i), &allow_gc); 999 } 1000 } 1001 } 1002 } else if (object->IsJSGlobalObject()) { 1003 enum_keys = GetOwnEnumPropertyDictionaryKeys( 1004 isolate_, mode_, this, object, 1005 JSGlobalObject::cast(*object).global_dictionary(kAcquireLoad)); 1006 } else if (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) { 1007 enum_keys = GetOwnEnumPropertyDictionaryKeys( 1008 isolate_, mode_, this, object, object->property_dictionary_swiss()); 1009 } else { 1010 enum_keys = GetOwnEnumPropertyDictionaryKeys( 1011 isolate_, mode_, this, object, object->property_dictionary()); 1012 } 1013 if (object->IsJSModuleNamespace()) { 1014 // Simulate [[GetOwnProperty]] for establishing enumerability, which 1015 // throws for uninitialized exports. 1016 for (int i = 0, n = enum_keys->length(); i < n; ++i) { 1017 Handle<String> key(String::cast(enum_keys->get(i)), isolate_); 1018 if (Handle<JSModuleNamespace>::cast(object) 1019 ->GetExport(isolate(), key) 1020 .is_null()) { 1021 return Nothing<bool>(); 1022 } 1023 } 1024 } 1025 RETURN_NOTHING_IF_NOT_SUCCESSFUL(AddKeys(enum_keys, DO_NOT_CONVERT)); 1026 } else { 1027 if (object->HasFastProperties()) { 1028 int limit = object->map().NumberOfOwnDescriptors(); 1029 Handle<DescriptorArray> descs( 1030 object->map().instance_descriptors(isolate_), isolate_); 1031 // First collect the strings, 1032 base::Optional<int> first_symbol = 1033 CollectOwnPropertyNamesInternal<true>(object, this, descs, 0, limit); 1034 // then the symbols. 1035 RETURN_NOTHING_IF_NOT_SUCCESSFUL(first_symbol); 1036 if (first_symbol.value() != -1) { 1037 RETURN_NOTHING_IF_NOT_SUCCESSFUL(CollectOwnPropertyNamesInternal<false>( 1038 object, this, descs, first_symbol.value(), limit)); 1039 } 1040 } else if (object->IsJSGlobalObject()) { 1041 RETURN_NOTHING_IF_NOT_SUCCESSFUL(CollectKeysFromDictionary( 1042 handle(JSGlobalObject::cast(*object).global_dictionary(kAcquireLoad), 1043 isolate_), 1044 this)); 1045 } else if (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) { 1046 RETURN_NOTHING_IF_NOT_SUCCESSFUL(CollectKeysFromDictionary( 1047 handle(object->property_dictionary_swiss(), isolate_), this)); 1048 } else { 1049 RETURN_NOTHING_IF_NOT_SUCCESSFUL(CollectKeysFromDictionary( 1050 handle(object->property_dictionary(), isolate_), this)); 1051 } 1052 } 1053 // Add the property keys from the interceptor. 1054 return CollectInterceptorKeys(receiver, object, kNamed); 1055} 1056 1057ExceptionStatus KeyAccumulator::CollectPrivateNames(Handle<JSReceiver> receiver, 1058 Handle<JSObject> object) { 1059 DCHECK_EQ(mode_, KeyCollectionMode::kOwnOnly); 1060 if (object->HasFastProperties()) { 1061 int limit = object->map().NumberOfOwnDescriptors(); 1062 Handle<DescriptorArray> descs(object->map().instance_descriptors(isolate_), 1063 isolate_); 1064 CollectOwnPropertyNamesInternal<false>(object, this, descs, 0, limit); 1065 } else if (object->IsJSGlobalObject()) { 1066 RETURN_FAILURE_IF_NOT_SUCCESSFUL(CollectKeysFromDictionary( 1067 handle(JSGlobalObject::cast(*object).global_dictionary(kAcquireLoad), 1068 isolate_), 1069 this)); 1070 } else if (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) { 1071 RETURN_FAILURE_IF_NOT_SUCCESSFUL(CollectKeysFromDictionary( 1072 handle(object->property_dictionary_swiss(), isolate_), this)); 1073 } else { 1074 RETURN_FAILURE_IF_NOT_SUCCESSFUL(CollectKeysFromDictionary( 1075 handle(object->property_dictionary(), isolate_), this)); 1076 } 1077 return ExceptionStatus::kSuccess; 1078} 1079 1080Maybe<bool> KeyAccumulator::CollectAccessCheckInterceptorKeys( 1081 Handle<AccessCheckInfo> access_check_info, Handle<JSReceiver> receiver, 1082 Handle<JSObject> object) { 1083 if (!skip_indices_) { 1084 MAYBE_RETURN((CollectInterceptorKeysInternal( 1085 receiver, object, 1086 handle(InterceptorInfo::cast( 1087 access_check_info->indexed_interceptor()), 1088 isolate_), 1089 kIndexed)), 1090 Nothing<bool>()); 1091 } 1092 MAYBE_RETURN( 1093 (CollectInterceptorKeysInternal( 1094 receiver, object, 1095 handle(InterceptorInfo::cast(access_check_info->named_interceptor()), 1096 isolate_), 1097 kNamed)), 1098 Nothing<bool>()); 1099 return Just(true); 1100} 1101 1102// Returns |true| on success, |false| if prototype walking should be stopped, 1103// |nothing| if an exception was thrown. 1104Maybe<bool> KeyAccumulator::CollectOwnKeys(Handle<JSReceiver> receiver, 1105 Handle<JSObject> object) { 1106 // Check access rights if required. 1107 if (object->IsAccessCheckNeeded() && 1108 !isolate_->MayAccess(handle(isolate_->context(), isolate_), object)) { 1109 // The cross-origin spec says that [[Enumerate]] shall return an empty 1110 // iterator when it doesn't have access... 1111 if (mode_ == KeyCollectionMode::kIncludePrototypes) { 1112 return Just(false); 1113 } 1114 // ...whereas [[OwnPropertyKeys]] shall return allowlisted properties. 1115 DCHECK_EQ(KeyCollectionMode::kOwnOnly, mode_); 1116 Handle<AccessCheckInfo> access_check_info; 1117 { 1118 DisallowGarbageCollection no_gc; 1119 AccessCheckInfo maybe_info = AccessCheckInfo::Get(isolate_, object); 1120 if (!maybe_info.is_null()) { 1121 access_check_info = handle(maybe_info, isolate_); 1122 } 1123 } 1124 // We always have both kinds of interceptors or none. 1125 if (!access_check_info.is_null() && 1126 access_check_info->named_interceptor() != Object()) { 1127 MAYBE_RETURN(CollectAccessCheckInterceptorKeys(access_check_info, 1128 receiver, object), 1129 Nothing<bool>()); 1130 return Just(false); 1131 } 1132 filter_ = static_cast<PropertyFilter>(filter_ | ONLY_ALL_CAN_READ); 1133 } 1134 if (filter_ & PRIVATE_NAMES_ONLY) { 1135 RETURN_NOTHING_IF_NOT_SUCCESSFUL(CollectPrivateNames(receiver, object)); 1136 return Just(true); 1137 } 1138 1139 if (may_have_elements_) { 1140 MAYBE_RETURN(CollectOwnElementIndices(receiver, object), Nothing<bool>()); 1141 } 1142 MAYBE_RETURN(CollectOwnPropertyNames(receiver, object), Nothing<bool>()); 1143 return Just(true); 1144} 1145 1146// static 1147Handle<FixedArray> KeyAccumulator::GetOwnEnumPropertyKeys( 1148 Isolate* isolate, Handle<JSObject> object) { 1149 if (object->HasFastProperties()) { 1150 return GetFastEnumPropertyKeys(isolate, object); 1151 } else if (object->IsJSGlobalObject()) { 1152 return GetOwnEnumPropertyDictionaryKeys( 1153 isolate, KeyCollectionMode::kOwnOnly, nullptr, object, 1154 JSGlobalObject::cast(*object).global_dictionary(kAcquireLoad)); 1155 } else if (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) { 1156 return GetOwnEnumPropertyDictionaryKeys( 1157 isolate, KeyCollectionMode::kOwnOnly, nullptr, object, 1158 object->property_dictionary_swiss()); 1159 } else { 1160 return GetOwnEnumPropertyDictionaryKeys( 1161 isolate, KeyCollectionMode::kOwnOnly, nullptr, object, 1162 object->property_dictionary()); 1163 } 1164} 1165 1166namespace { 1167 1168class NameComparator { 1169 public: 1170 explicit NameComparator(Isolate* isolate) : isolate_(isolate) {} 1171 1172 bool operator()(uint32_t hash1, uint32_t hash2, const Handle<Name>& key1, 1173 const Handle<Name>& key2) const { 1174 return Name::Equals(isolate_, key1, key2); 1175 } 1176 1177 private: 1178 Isolate* isolate_; 1179}; 1180 1181} // namespace 1182 1183// ES6 #sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeys 1184// Returns |true| on success, |nothing| in case of exception. 1185Maybe<bool> KeyAccumulator::CollectOwnJSProxyKeys(Handle<JSReceiver> receiver, 1186 Handle<JSProxy> proxy) { 1187 STACK_CHECK(isolate_, Nothing<bool>()); 1188 if (filter_ == PRIVATE_NAMES_ONLY) { 1189 if (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) { 1190 RETURN_NOTHING_IF_NOT_SUCCESSFUL(CollectKeysFromDictionary( 1191 handle(proxy->property_dictionary_swiss(), isolate_), this)); 1192 } else { 1193 RETURN_NOTHING_IF_NOT_SUCCESSFUL(CollectKeysFromDictionary( 1194 handle(proxy->property_dictionary(), isolate_), this)); 1195 } 1196 return Just(true); 1197 } 1198 1199 // 1. Let handler be the value of the [[ProxyHandler]] internal slot of O. 1200 Handle<Object> handler(proxy->handler(), isolate_); 1201 // 2. If handler is null, throw a TypeError exception. 1202 // 3. Assert: Type(handler) is Object. 1203 if (proxy->IsRevoked()) { 1204 isolate_->Throw(*isolate_->factory()->NewTypeError( 1205 MessageTemplate::kProxyRevoked, isolate_->factory()->ownKeys_string())); 1206 return Nothing<bool>(); 1207 } 1208 // 4. Let target be the value of the [[ProxyTarget]] internal slot of O. 1209 Handle<JSReceiver> target(JSReceiver::cast(proxy->target()), isolate_); 1210 // 5. Let trap be ? GetMethod(handler, "ownKeys"). 1211 Handle<Object> trap; 1212 ASSIGN_RETURN_ON_EXCEPTION_VALUE( 1213 isolate_, trap, 1214 Object::GetMethod(Handle<JSReceiver>::cast(handler), 1215 isolate_->factory()->ownKeys_string()), 1216 Nothing<bool>()); 1217 // 6. If trap is undefined, then 1218 if (trap->IsUndefined(isolate_)) { 1219 // 6a. Return target.[[OwnPropertyKeys]](). 1220 return CollectOwnJSProxyTargetKeys(proxy, target); 1221 } 1222 // 7. Let trapResultArray be Call(trap, handler, «target»). 1223 Handle<Object> trap_result_array; 1224 Handle<Object> args[] = {target}; 1225 ASSIGN_RETURN_ON_EXCEPTION_VALUE( 1226 isolate_, trap_result_array, 1227 Execution::Call(isolate_, trap, handler, arraysize(args), args), 1228 Nothing<bool>()); 1229 // 8. Let trapResult be ? CreateListFromArrayLike(trapResultArray, 1230 // «String, Symbol»). 1231 Handle<FixedArray> trap_result; 1232 ASSIGN_RETURN_ON_EXCEPTION_VALUE( 1233 isolate_, trap_result, 1234 Object::CreateListFromArrayLike(isolate_, trap_result_array, 1235 ElementTypes::kStringAndSymbol), 1236 Nothing<bool>()); 1237 // 9. If trapResult contains any duplicate entries, throw a TypeError 1238 // exception. Combine with step 18 1239 // 18. Let uncheckedResultKeys be a new List which is a copy of trapResult. 1240 Zone set_zone(isolate_->allocator(), ZONE_NAME); 1241 1242 const int kPresent = 1; 1243 const int kGone = 0; 1244 using ZoneHashMapImpl = 1245 base::TemplateHashMapImpl<Handle<Name>, int, NameComparator, 1246 ZoneAllocationPolicy>; 1247 ZoneHashMapImpl unchecked_result_keys( 1248 ZoneHashMapImpl::kDefaultHashMapCapacity, NameComparator(isolate_), 1249 ZoneAllocationPolicy(&set_zone)); 1250 int unchecked_result_keys_size = 0; 1251 for (int i = 0; i < trap_result->length(); ++i) { 1252 Handle<Name> key(Name::cast(trap_result->get(i)), isolate_); 1253 auto entry = unchecked_result_keys.LookupOrInsert(key, key->EnsureHash()); 1254 if (entry->value != kPresent) { 1255 entry->value = kPresent; 1256 unchecked_result_keys_size++; 1257 } else { 1258 // found dupes, throw exception 1259 isolate_->Throw(*isolate_->factory()->NewTypeError( 1260 MessageTemplate::kProxyOwnKeysDuplicateEntries)); 1261 return Nothing<bool>(); 1262 } 1263 } 1264 // 10. Let extensibleTarget be ? IsExtensible(target). 1265 Maybe<bool> maybe_extensible = JSReceiver::IsExtensible(target); 1266 MAYBE_RETURN(maybe_extensible, Nothing<bool>()); 1267 bool extensible_target = maybe_extensible.FromJust(); 1268 // 11. Let targetKeys be ? target.[[OwnPropertyKeys]](). 1269 Handle<FixedArray> target_keys; 1270 ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate_, target_keys, 1271 JSReceiver::OwnPropertyKeys(target), 1272 Nothing<bool>()); 1273 // 12, 13. (Assert) 1274 // 14. Let targetConfigurableKeys be an empty List. 1275 // To save memory, we're re-using target_keys and will modify it in-place. 1276 Handle<FixedArray> target_configurable_keys = target_keys; 1277 // 15. Let targetNonconfigurableKeys be an empty List. 1278 Handle<FixedArray> target_nonconfigurable_keys = 1279 isolate_->factory()->NewFixedArray(target_keys->length()); 1280 int nonconfigurable_keys_length = 0; 1281 // 16. Repeat, for each element key of targetKeys: 1282 for (int i = 0; i < target_keys->length(); ++i) { 1283 // 16a. Let desc be ? target.[[GetOwnProperty]](key). 1284 PropertyDescriptor desc; 1285 Maybe<bool> found = JSReceiver::GetOwnPropertyDescriptor( 1286 isolate_, target, handle(target_keys->get(i), isolate_), &desc); 1287 MAYBE_RETURN(found, Nothing<bool>()); 1288 // 16b. If desc is not undefined and desc.[[Configurable]] is false, then 1289 if (found.FromJust() && !desc.configurable()) { 1290 // 16b i. Append key as an element of targetNonconfigurableKeys. 1291 target_nonconfigurable_keys->set(nonconfigurable_keys_length, 1292 target_keys->get(i)); 1293 nonconfigurable_keys_length++; 1294 // The key was moved, null it out in the original list. 1295 target_keys->set(i, Smi::zero()); 1296 } else { 1297 // 16c. Else, 1298 // 16c i. Append key as an element of targetConfigurableKeys. 1299 // (No-op, just keep it in |target_keys|.) 1300 } 1301 } 1302 // 17. If extensibleTarget is true and targetNonconfigurableKeys is empty, 1303 // then: 1304 if (extensible_target && nonconfigurable_keys_length == 0) { 1305 // 17a. Return trapResult. 1306 return AddKeysFromJSProxy(proxy, trap_result); 1307 } 1308 // 18. (Done in step 9) 1309 // 19. Repeat, for each key that is an element of targetNonconfigurableKeys: 1310 for (int i = 0; i < nonconfigurable_keys_length; ++i) { 1311 Object raw_key = target_nonconfigurable_keys->get(i); 1312 Handle<Name> key(Name::cast(raw_key), isolate_); 1313 // 19a. If key is not an element of uncheckedResultKeys, throw a 1314 // TypeError exception. 1315 auto found = unchecked_result_keys.Lookup(key, key->hash()); 1316 if (found == nullptr || found->value == kGone) { 1317 isolate_->Throw(*isolate_->factory()->NewTypeError( 1318 MessageTemplate::kProxyOwnKeysMissing, key)); 1319 return Nothing<bool>(); 1320 } 1321 // 19b. Remove key from uncheckedResultKeys. 1322 found->value = kGone; 1323 unchecked_result_keys_size--; 1324 } 1325 // 20. If extensibleTarget is true, return trapResult. 1326 if (extensible_target) { 1327 return AddKeysFromJSProxy(proxy, trap_result); 1328 } 1329 // 21. Repeat, for each key that is an element of targetConfigurableKeys: 1330 for (int i = 0; i < target_configurable_keys->length(); ++i) { 1331 Object raw_key = target_configurable_keys->get(i); 1332 if (raw_key.IsSmi()) continue; // Zapped entry, was nonconfigurable. 1333 Handle<Name> key(Name::cast(raw_key), isolate_); 1334 // 21a. If key is not an element of uncheckedResultKeys, throw a 1335 // TypeError exception. 1336 auto found = unchecked_result_keys.Lookup(key, key->hash()); 1337 if (found == nullptr || found->value == kGone) { 1338 isolate_->Throw(*isolate_->factory()->NewTypeError( 1339 MessageTemplate::kProxyOwnKeysMissing, key)); 1340 return Nothing<bool>(); 1341 } 1342 // 21b. Remove key from uncheckedResultKeys. 1343 found->value = kGone; 1344 unchecked_result_keys_size--; 1345 } 1346 // 22. If uncheckedResultKeys is not empty, throw a TypeError exception. 1347 if (unchecked_result_keys_size != 0) { 1348 DCHECK_GT(unchecked_result_keys_size, 0); 1349 isolate_->Throw(*isolate_->factory()->NewTypeError( 1350 MessageTemplate::kProxyOwnKeysNonExtensible)); 1351 return Nothing<bool>(); 1352 } 1353 // 23. Return trapResult. 1354 return AddKeysFromJSProxy(proxy, trap_result); 1355} 1356 1357Maybe<bool> KeyAccumulator::CollectOwnJSProxyTargetKeys( 1358 Handle<JSProxy> proxy, Handle<JSReceiver> target) { 1359 // TODO(cbruni): avoid creating another KeyAccumulator 1360 Handle<FixedArray> keys; 1361 ASSIGN_RETURN_ON_EXCEPTION_VALUE( 1362 isolate_, keys, 1363 KeyAccumulator::GetKeys( 1364 target, KeyCollectionMode::kOwnOnly, ALL_PROPERTIES, 1365 GetKeysConversion::kConvertToString, is_for_in_, skip_indices_), 1366 Nothing<bool>()); 1367 Maybe<bool> result = AddKeysFromJSProxy(proxy, keys); 1368 return result; 1369} 1370 1371#undef RETURN_NOTHING_IF_NOT_SUCCESSFUL 1372#undef RETURN_FAILURE_IF_NOT_SUCCESSFUL 1373} // namespace internal 1374} // namespace v8 1375