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