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