keys.cc 33.7 KB
Newer Older
1 2 3 4
// Copyright 2013 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
#include "src/keys.h"
6

7
#include "src/api-arguments.h"
8 9
#include "src/elements.h"
#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 17 18 19 20 21

namespace v8 {
namespace internal {

KeyAccumulator::~KeyAccumulator() {
}

22 23 24 25 26 27 28 29 30 31 32 33
namespace {

static bool ContainsOnlyValidKeys(Handle<FixedArray> array) {
  int len = array->length();
  for (int i = 0; i < len; i++) {
    Object* e = array->get(i);
    if (!(e->IsName() || e->IsNumber())) return false;
  }
  return true;
}

}  // 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 48
    return isolate_->factory()->empty_fixed_array();
  }
49
  if (mode_ == KeyCollectionMode::kOwnOnly &&
50 51
      keys_->map() == isolate_->heap()->fixed_array_map()) {
    return Handle<FixedArray>::cast(keys_);
52
  }
53 54 55 56 57
  USE(ContainsOnlyValidKeys);
  Handle<FixedArray> result =
      OrderedHashSet::ConvertToKeysArray(keys(), convert);
  DCHECK(ContainsOnlyValidKeys(result));
  return result;
58 59
}

60 61
void KeyAccumulator::AddKey(Object* key, AddKeyConversion convert) {
  AddKey(handle(key, isolate_), convert);
62 63
}

64
void KeyAccumulator::AddKey(Handle<Object> key, AddKeyConversion convert) {
65
  if (key->IsSymbol()) {
66 67 68 69
    if (filter_ & SKIP_SYMBOLS) return;
    if (Handle<Symbol>::cast(key)->is_private()) return;
  } else if (filter_ & SKIP_STRINGS) {
    return;
70
  }
71
  if (IsShadowed(key)) return;
72 73
  if (keys_.is_null()) {
    keys_ = OrderedHashSet::Allocate(isolate_, 16);
74
  }
75 76 77 78
  uint32_t index;
  if (convert == CONVERT_TO_ARRAY_INDEX && key->IsString() &&
      Handle<String>::cast(key)->AsArrayIndex(&index)) {
    key = isolate_->factory()->NewNumberFromUint(index);
79
  }
80
  keys_ = OrderedHashSet::Add(keys(), key);
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
}

void KeyAccumulator::AddKeys(Handle<FixedArray> array,
                             AddKeyConversion convert) {
  int add_length = array->length();
  for (int i = 0; i < add_length; i++) {
    Handle<Object> current(array->get(i), isolate_);
    AddKey(current, convert);
  }
}

void KeyAccumulator::AddKeys(Handle<JSObject> array_like,
                             AddKeyConversion convert) {
  DCHECK(array_like->IsJSArray() || array_like->HasSloppyArgumentsElements());
  ElementsAccessor* accessor = array_like->GetElementsAccessor();
  accessor->AddElementsToKeyAccumulator(array_like, this, convert);
}

99 100
MaybeHandle<FixedArray> FilterProxyKeys(KeyAccumulator* accumulator,
                                        Handle<JSProxy> owner,
101
                                        Handle<FixedArray> keys,
102 103
                                        PropertyFilter filter) {
  if (filter == ALL_PROPERTIES) {
104 105 106
    // Nothing to do.
    return keys;
  }
107
  Isolate* isolate = accumulator->isolate();
108 109 110
  int store_position = 0;
  for (int i = 0; i < keys->length(); ++i) {
    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 118
      if (!found.FromJust()) continue;
      if (!desc.enumerable()) {
119
        accumulator->AddShadowingKey(key);
120 121
        continue;
      }
122 123 124 125 126 127 128 129 130 131 132 133
    }
    // Keep this key.
    if (store_position != i) {
      keys->set(store_position, *key);
    }
    store_position++;
  }
  if (store_position == 0) return isolate->factory()->empty_fixed_array();
  keys->Shrink(store_position);
  return keys;
}

134
// Returns "nothing" in case of exception, "true" on success.
135 136
Maybe<bool> KeyAccumulator::AddKeysFromJSProxy(Handle<JSProxy> proxy,
                                               Handle<FixedArray> keys) {
137 138
  // Postpone the enumerable check for for-in to the ForInFilter step.
  if (!is_for_in_) {
139
    ASSIGN_RETURN_ON_EXCEPTION_VALUE(
140
        isolate_, keys, FilterProxyKeys(this, proxy, keys, filter_),
141
        Nothing<bool>());
142 143 144 145 146
    if (mode_ == KeyCollectionMode::kOwnOnly) {
      // If we collect only the keys from a JSProxy do not sort or deduplicate.
      keys_ = keys;
      return Just(true);
    }
147
  }
148
  AddKeys(keys, is_for_in_ ? CONVERT_TO_ARRAY_INDEX : DO_NOT_CONVERT);
149
  return Just(true);
150 151
}

152 153
Maybe<bool> KeyAccumulator::CollectKeys(Handle<JSReceiver> receiver,
                                        Handle<JSReceiver> object) {
154 155 156
  // Proxies have no hidden prototype and we should not trigger the
  // [[GetPrototypeOf]] trap on the last iteration when using
  // AdvanceFollowingProxies.
157
  if (mode_ == KeyCollectionMode::kOwnOnly && object->IsJSProxy()) {
158 159
    MAYBE_RETURN(CollectOwnJSProxyKeys(receiver, Handle<JSProxy>::cast(object)),
                 Nothing<bool>());
160 161 162
    return Just(true);
  }

163
  PrototypeIterator::WhereToEnd end = mode_ == KeyCollectionMode::kOwnOnly
164 165
                                          ? PrototypeIterator::END_AT_NON_HIDDEN
                                          : PrototypeIterator::END_AT_NULL;
166
  for (PrototypeIterator iter(isolate_, object, kStartAtReceiver, end);
167
       !iter.IsAtEnd();) {
168 169 170
    // Start the shadow checks only after the first prototype has added
    // shadowing keys.
    if (HasShadowingKeys()) skip_shadow_check_ = false;
171 172 173 174
    Handle<JSReceiver> current =
        PrototypeIterator::GetCurrent<JSReceiver>(iter);
    Maybe<bool> result = Just(false);  // Dummy initialization.
    if (current->IsJSProxy()) {
175
      result = CollectOwnJSProxyKeys(receiver, Handle<JSProxy>::cast(current));
176 177
    } else {
      DCHECK(current->IsJSObject());
178
      result = CollectOwnKeys(receiver, Handle<JSObject>::cast(current));
179 180 181 182
    }
    MAYBE_RETURN(result, Nothing<bool>());
    if (!result.FromJust()) break;  // |false| means "stop iterating".
    // 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 185 186
    if (!iter.AdvanceFollowingProxiesIgnoringAccessChecks()) {
      return Nothing<bool>();
    }
187 188 189 190
    if (!last_non_empty_prototype_.is_null() &&
        *last_non_empty_prototype_ == *current) {
      break;
    }
191 192 193 194
  }
  return Just(true);
}

195 196
bool KeyAccumulator::HasShadowingKeys() { return !shadowing_keys_.is_null(); }

197
bool KeyAccumulator::IsShadowed(Handle<Object> key) {
198 199
  if (!HasShadowingKeys() || skip_shadow_check_) return false;
  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 209
  if (shadowing_keys_.is_null()) {
    shadowing_keys_ = ObjectHashSet::New(isolate_, 16);
210
  }
211
  shadowing_keys_ = ObjectHashSet::Add(shadowing_keys_, key);
212 213
}

214 215 216 217 218 219 220
namespace {

void TrySettingEmptyEnumCache(JSReceiver* object) {
  Map* map = object->map();
  DCHECK_EQ(kInvalidEnumCacheSentinel, map->EnumLength());
  if (!map->OnlyHasSimpleProperties()) return;
  if (map->IsJSProxyMap()) return;
221
  if (map->NumberOfEnumerableProperties() > 0) return;
222 223 224 225
  DCHECK(object->IsJSObject());
  map->SetEnumLength(0);
}

226
bool CheckAndInitalizeEmptyEnumCache(JSReceiver* object) {
227 228 229 230 231 232 233 234 235 236 237 238
  if (object->map()->EnumLength() == kInvalidEnumCacheSentinel) {
    TrySettingEmptyEnumCache(object);
  }
  if (object->map()->EnumLength() != 0) return false;
  DCHECK(object->IsJSObject());
  return !JSObject::cast(object)->HasEnumerableElements();
}
}  // namespace

void FastKeyAccumulator::Prepare() {
  DisallowHeapAllocation no_gc;
  // Directly go for the fast path for OWN_ONLY keys.
239
  if (mode_ == KeyCollectionMode::kOwnOnly) return;
240 241 242
  // Fully walk the prototype chain and find the last prototype with keys.
  is_receiver_simple_enum_ = false;
  has_empty_prototype_ = true;
243
  JSReceiver* last_prototype = nullptr;
244 245 246
  for (PrototypeIterator iter(isolate_, *receiver_); !iter.IsAtEnd();
       iter.Advance()) {
    JSReceiver* current = iter.GetCurrent<JSReceiver>();
247
    bool has_no_properties = CheckAndInitalizeEmptyEnumCache(current);
248 249
    if (has_no_properties) continue;
    last_prototype = current;
250 251
    has_empty_prototype_ = false;
  }
252 253 254 255 256 257 258
  if (has_empty_prototype_) {
    is_receiver_simple_enum_ =
        receiver_->map()->EnumLength() != kInvalidEnumCacheSentinel &&
        !JSObject::cast(*receiver_)->HasEnumerableElements();
  } else if (last_prototype != nullptr) {
    last_non_empty_prototype_ = handle(last_prototype, isolate_);
  }
259 260 261
}

namespace {
262 263 264 265 266 267 268 269
static Handle<FixedArray> ReduceFixedArrayTo(Isolate* isolate,
                                             Handle<FixedArray> array,
                                             int length) {
  DCHECK_LE(length, array->length());
  if (array->length() == length) return array;
  return isolate->factory()->CopyFixedArrayUpTo(array, length);
}

270 271
// Initializes and directly returns the enume cache. Users of this function
// have to make sure to never directly leak the enum cache.
272 273 274 275 276 277 278 279 280 281 282 283 284
Handle<FixedArray> GetFastEnumPropertyKeys(Isolate* isolate,
                                           Handle<JSObject> object) {
  Handle<Map> map(object->map());
  bool cache_enum_length = map->OnlyHasSimpleProperties();

  Handle<DescriptorArray> descs =
      Handle<DescriptorArray>(map->instance_descriptors(), isolate);
  int own_property_count = map->EnumLength();
  // If the enum length of the given map is set to kInvalidEnumCache, this
  // means that the map itself has never used the present enum cache. The
  // first step to using the cache is to set the enum length of the map by
  // counting the number of own descriptors that are ENUMERABLE_STRINGS.
  if (own_property_count == kInvalidEnumCacheSentinel) {
285
    own_property_count = map->NumberOfEnumerableProperties();
286
  } else {
287
    DCHECK_EQ(own_property_count, map->NumberOfEnumerableProperties());
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
  }

  if (descs->HasEnumCache()) {
    Handle<FixedArray> keys(descs->GetEnumCache(), isolate);
    // In case the number of properties required in the enum are actually
    // present, we can reuse the enum cache. Otherwise, this means that the
    // enum cache was generated for a previous (smaller) version of the
    // Descriptor Array. In that case we regenerate the enum cache.
    if (own_property_count <= keys->length()) {
      isolate->counters()->enum_cache_hits()->Increment();
      if (cache_enum_length) map->SetEnumLength(own_property_count);
      return ReduceFixedArrayTo(isolate, keys, own_property_count);
    }
  }

  if (descs->IsEmpty()) {
    isolate->counters()->enum_cache_hits()->Increment();
    if (cache_enum_length) map->SetEnumLength(0);
    return isolate->factory()->empty_fixed_array();
  }

  isolate->counters()->enum_cache_misses()->Increment();

  Handle<FixedArray> storage =
      isolate->factory()->NewFixedArray(own_property_count);
  Handle<FixedArray> indices =
      isolate->factory()->NewFixedArray(own_property_count);

  int size = map->NumberOfOwnDescriptors();
  int index = 0;

  for (int i = 0; i < size; i++) {
    PropertyDetails details = descs->GetDetails(i);
    if (details.IsDontEnum()) continue;
    Object* key = descs->GetKey(i);
    if (key->IsSymbol()) continue;
    storage->set(index, key);
    if (!indices.is_null()) {
326 327
      if (details.location() == kField) {
        DCHECK_EQ(kData, details.kind());
328 329 330
        FieldIndex field_index = FieldIndex::ForDescriptor(*map, i);
        int load_by_field_index = field_index.GetLoadByFieldIndex();
        indices->set(index, Smi::FromInt(load_by_field_index));
331 332
      } else {
        indices = Handle<FixedArray>();
333 334 335 336 337 338 339 340 341 342 343 344
      }
    }
    index++;
  }
  DCHECK(index == storage->length());

  DescriptorArray::SetEnumCache(descs, isolate, storage, indices);
  if (cache_enum_length) {
    map->SetEnumLength(own_property_count);
  }
  return storage;
}
345 346

template <bool fast_properties>
347 348 349
MaybeHandle<FixedArray> GetOwnKeysWithElements(Isolate* isolate,
                                               Handle<JSObject> object,
                                               GetKeysConversion convert) {
350
  Handle<FixedArray> keys;
351
  ElementsAccessor* accessor = object->GetElementsAccessor();
352
  if (fast_properties) {
353
    keys = GetFastEnumPropertyKeys(isolate, object);
354 355
  } else {
    // TODO(cbruni): preallocate big enough array to also hold elements.
356
    keys = KeyAccumulator::GetOwnEnumPropertyKeys(isolate, object);
357
  }
358
  MaybeHandle<FixedArray> result =
359 360 361 362
      accessor->PrependElementIndices(object, keys, convert, ONLY_ENUMERABLE);

  if (FLAG_trace_for_in_enumerate) {
    PrintF("| strings=%d symbols=0 elements=%u || prototypes>=1 ||\n",
363
           keys->length(), result.ToHandleChecked()->length() - keys->length());
364 365
  }
  return result;
366 367
}

368 369 370 371
bool OnlyHasSimpleProperties(Map* map) {
  return map->instance_type() > LAST_CUSTOM_ELEMENTS_RECEIVER;
}

372 373
}  // namespace

374 375
MaybeHandle<FixedArray> FastKeyAccumulator::GetKeys(
    GetKeysConversion keys_conversion) {
376 377 378 379 380 381
  if (filter_ == ENUMERABLE_STRINGS) {
    Handle<FixedArray> keys;
    if (GetKeysFast(keys_conversion).ToHandle(&keys)) {
      return keys;
    }
    if (isolate_->has_pending_exception()) return MaybeHandle<FixedArray>();
382
  }
383

384
  return GetKeysSlow(keys_conversion);
385 386 387
}

MaybeHandle<FixedArray> FastKeyAccumulator::GetKeysFast(
388
    GetKeysConversion keys_conversion) {
389
  bool own_only = has_empty_prototype_ || mode_ == KeyCollectionMode::kOwnOnly;
390 391
  Map* map = receiver_->map();
  if (!own_only || !OnlyHasSimpleProperties(map)) {
392 393 394
    return MaybeHandle<FixedArray>();
  }

395
  // From this point on we are certiain to only collect own keys.
396 397 398
  DCHECK(receiver_->IsJSObject());
  Handle<JSObject> object = Handle<JSObject>::cast(receiver_);

399 400
  // Do not try to use the enum-cache for dict-mode objects.
  if (map->is_dictionary_map()) {
401
    return GetOwnKeysWithElements<false>(isolate_, object, keys_conversion);
402
  }
403 404
  int enum_length = receiver_->map()->EnumLength();
  if (enum_length == kInvalidEnumCacheSentinel) {
405
    Handle<FixedArray> keys;
406
    // Try initializing the enum cache and return own properties.
407
    if (GetOwnKeysWithUninitializedEnumCache().ToHandle(&keys)) {
408 409 410 411
      if (FLAG_trace_for_in_enumerate) {
        PrintF("| strings=%d symbols=0 elements=0 || prototypes>=1 ||\n",
               keys->length());
      }
412 413 414 415 416 417 418
      is_receiver_simple_enum_ =
          object->map()->EnumLength() != kInvalidEnumCacheSentinel;
      return keys;
    }
  }
  // The properties-only case failed because there were probably elements on the
  // receiver.
419
  return GetOwnKeysWithElements<true>(isolate_, object, keys_conversion);
420 421
}

422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443
MaybeHandle<FixedArray>
FastKeyAccumulator::GetOwnKeysWithUninitializedEnumCache() {
  Handle<JSObject> object = Handle<JSObject>::cast(receiver_);
  // Uninitalized enum cache
  Map* map = object->map();
  if (object->elements()->length() != 0) {
    // Assume that there are elements.
    return MaybeHandle<FixedArray>();
  }
  int number_of_own_descriptors = map->NumberOfOwnDescriptors();
  if (number_of_own_descriptors == 0) {
    map->SetEnumLength(0);
    return isolate_->factory()->empty_fixed_array();
  }
  // We have no elements but possibly enumerable property keys, hence we can
  // directly initialize the enum cache.
  Handle<FixedArray> keys = GetFastEnumPropertyKeys(isolate_, object);
  if (is_for_in_) return keys;
  // Do not leak the enum cache as it might end up as an elements backing store.
  return isolate_->factory()->CopyFixedArray(keys);
}

444
MaybeHandle<FixedArray> FastKeyAccumulator::GetKeysSlow(
445 446 447 448 449 450 451 452
    GetKeysConversion keys_conversion) {
  KeyAccumulator accumulator(isolate_, mode_, filter_);
  accumulator.set_is_for_in(is_for_in_);
  accumulator.set_last_non_empty_prototype(last_non_empty_prototype_);

  MAYBE_RETURN(accumulator.CollectKeys(receiver_, receiver_),
               MaybeHandle<FixedArray>());
  return accumulator.GetKeys(keys_conversion);
453
}
454

455 456
namespace {

457 458 459
enum IndexedOrNamed { kIndexed, kNamed };

// Returns |true| on success, |nothing| on exception.
460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482
template <class Callback, IndexedOrNamed type>
Maybe<bool> CollectInterceptorKeysInternal(Handle<JSReceiver> receiver,
                                           Handle<JSObject> object,
                                           Handle<InterceptorInfo> interceptor,
                                           KeyAccumulator* accumulator) {
  Isolate* isolate = accumulator->isolate();
  PropertyCallbackArguments args(isolate, interceptor->data(), *receiver,
                                 *object, Object::DONT_THROW);
  Handle<JSObject> result;
  if (!interceptor->enumerator()->IsUndefined(isolate)) {
    Callback enum_fun = v8::ToCData<Callback>(interceptor->enumerator());
    const char* log_tag = type == kIndexed ? "interceptor-indexed-enum"
                                           : "interceptor-named-enum";
    LOG(isolate, ApiObjectAccess(log_tag, *object));
    result = args.Call(enum_fun);
  }
  RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, Nothing<bool>());
  if (result.is_null()) return Just(true);
  accumulator->AddKeys(
      result, type == kIndexed ? CONVERT_TO_ARRAY_INDEX : DO_NOT_CONVERT);
  return Just(true);
}

483
template <class Callback, IndexedOrNamed type>
484
Maybe<bool> CollectInterceptorKeys(Handle<JSReceiver> receiver,
485 486
                                   Handle<JSObject> object,
                                   KeyAccumulator* accumulator) {
487 488 489 490 491 492 493 494 495 496 497 498 499 500
  Isolate* isolate = accumulator->isolate();
  if (type == kIndexed) {
    if (!object->HasIndexedInterceptor()) return Just(true);
  } else {
    if (!object->HasNamedInterceptor()) return Just(true);
  }
  Handle<InterceptorInfo> interceptor(type == kIndexed
                                          ? object->GetIndexedInterceptor()
                                          : object->GetNamedInterceptor(),
                                      isolate);
  if ((accumulator->filter() & ONLY_ALL_CAN_READ) &&
      !interceptor->all_can_read()) {
    return Just(true);
  }
501 502
  return CollectInterceptorKeysInternal<Callback, type>(
      receiver, object, interceptor, accumulator);
503 504
}

505 506 507 508 509 510
}  // namespace

Maybe<bool> KeyAccumulator::CollectOwnElementIndices(
    Handle<JSReceiver> receiver, Handle<JSObject> object) {
  if (filter_ & SKIP_STRINGS || skip_indices_) return Just(true);

511
  ElementsAccessor* accessor = object->GetElementsAccessor();
512
  accessor->CollectElementIndices(object, this);
513

514
  return CollectInterceptorKeys<v8::IndexedPropertyEnumeratorCallback,
515
                                kIndexed>(receiver, object, this);
516 517
}

518 519 520 521 522 523 524 525
namespace {

template <bool skip_symbols>
int CollectOwnPropertyNamesInternal(Handle<JSObject> object,
                                    KeyAccumulator* keys,
                                    Handle<DescriptorArray> descs,
                                    int start_index, int limit) {
  int first_skipped = -1;
526 527
  PropertyFilter filter = keys->filter();
  KeyCollectionMode mode = keys->mode();
528
  for (int i = start_index; i < limit; i++) {
529
    bool is_shadowing_key = false;
530
    PropertyDetails details = descs->GetDetails(i);
531 532 533 534 535 536 537 538 539 540

    if ((details.attributes() & filter) != 0) {
      if (mode == KeyCollectionMode::kIncludePrototypes) {
        is_shadowing_key = true;
      } else {
        continue;
      }
    }

    if (filter & ONLY_ALL_CAN_READ) {
541 542 543 544
      if (details.kind() != kAccessor) continue;
      Object* accessors = descs->GetValue(i);
      if (!accessors->IsAccessorInfo()) continue;
      if (!AccessorInfo::cast(accessors)->all_can_read()) continue;
545
    }
546

547 548 549 550 551 552
    Name* key = descs->GetKey(i);
    if (skip_symbols == key->IsSymbol()) {
      if (first_skipped == -1) first_skipped = i;
      continue;
    }
    if (key->FilterKey(keys->filter())) continue;
553 554

    if (is_shadowing_key) {
555
      keys->AddShadowingKey(key);
556 557 558
    } else {
      keys->AddKey(key, DO_NOT_CONVERT);
    }
559 560 561 562
  }
  return first_skipped;
}

563 564 565 566 567 568 569
template <class T>
Handle<FixedArray> GetOwnEnumPropertyDictionaryKeys(Isolate* isolate,
                                                    KeyCollectionMode mode,
                                                    KeyAccumulator* accumulator,
                                                    Handle<JSObject> object,
                                                    T* raw_dictionary) {
  Handle<T> dictionary(raw_dictionary, isolate);
570
  int length = dictionary->NumberOfEnumerableProperties();
571 572 573 574 575 576 577
  if (length == 0) {
    return isolate->factory()->empty_fixed_array();
  }
  Handle<FixedArray> storage = isolate->factory()->NewFixedArray(length);
  T::CopyEnumKeysTo(dictionary, storage, mode, accumulator);
  return storage;
}
578 579 580 581 582
}  // namespace

Maybe<bool> KeyAccumulator::CollectOwnPropertyNames(Handle<JSReceiver> receiver,
                                                    Handle<JSObject> object) {
  if (filter_ == ENUMERABLE_STRINGS) {
583 584 585 586 587 588 589 590 591 592 593 594 595 596
    Handle<FixedArray> enum_keys;
    if (object->HasFastProperties()) {
      enum_keys = KeyAccumulator::GetOwnEnumPropertyKeys(isolate_, object);
      // If the number of properties equals the length of enumerable properties
      // we do not have to filter out non-enumerable ones
      Map* map = object->map();
      int nof_descriptors = map->NumberOfOwnDescriptors();
      if (enum_keys->length() != nof_descriptors) {
        Handle<DescriptorArray> descs =
            Handle<DescriptorArray>(map->instance_descriptors(), isolate_);
        for (int i = 0; i < nof_descriptors; i++) {
          PropertyDetails details = descs->GetDetails(i);
          if (!details.IsDontEnum()) continue;
          Object* key = descs->GetKey(i);
597
          this->AddShadowingKey(key);
598 599 600 601
        }
      }
    } else if (object->IsJSGlobalObject()) {
      enum_keys = GetOwnEnumPropertyDictionaryKeys(
602 603
          isolate_, mode_, this, object,
          JSGlobalObject::cast(*object)->global_dictionary());
604 605 606 607
    } else {
      enum_keys = GetOwnEnumPropertyDictionaryKeys(
          isolate_, mode_, this, object, object->property_dictionary());
    }
608
    AddKeys(enum_keys, DO_NOT_CONVERT);
609
  } else {
610 611 612 613 614 615 616 617 618 619 620 621 622 623
    if (object->HasFastProperties()) {
      int limit = object->map()->NumberOfOwnDescriptors();
      Handle<DescriptorArray> descs(object->map()->instance_descriptors(),
                                    isolate_);
      // First collect the strings,
      int first_symbol =
          CollectOwnPropertyNamesInternal<true>(object, this, descs, 0, limit);
      // then the symbols.
      if (first_symbol != -1) {
        CollectOwnPropertyNamesInternal<false>(object, this, descs,
                                               first_symbol, limit);
      }
    } else if (object->IsJSGlobalObject()) {
      GlobalDictionary::CollectKeysTo(
624 625
          handle(JSGlobalObject::cast(*object)->global_dictionary(), isolate_),
          this);
626 627
    } else {
      NameDictionary::CollectKeysTo(
628
          handle(object->property_dictionary(), isolate_), this);
629
    }
630
  }
631
  // Add the property keys from the interceptor.
632
  return CollectInterceptorKeys<v8::GenericNamedPropertyEnumeratorCallback,
633
                                kNamed>(receiver, object, this);
634 635
}

636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658
Maybe<bool> KeyAccumulator::CollectAccessCheckInterceptorKeys(
    Handle<AccessCheckInfo> access_check_info, Handle<JSReceiver> receiver,
    Handle<JSObject> object) {
  MAYBE_RETURN(
      (CollectInterceptorKeysInternal<v8::IndexedPropertyEnumeratorCallback,
                                      kIndexed>(
          receiver, object,
          handle(
              InterceptorInfo::cast(access_check_info->indexed_interceptor()),
              isolate_),
          this)),
      Nothing<bool>());
  MAYBE_RETURN(
      (CollectInterceptorKeysInternal<
          v8::GenericNamedPropertyEnumeratorCallback, kNamed>(
          receiver, object,
          handle(InterceptorInfo::cast(access_check_info->named_interceptor()),
                 isolate_),
          this)),
      Nothing<bool>());
  return Just(true);
}

659 660
// Returns |true| on success, |false| if prototype walking should be stopped,
// |nothing| if an exception was thrown.
661 662
Maybe<bool> KeyAccumulator::CollectOwnKeys(Handle<JSReceiver> receiver,
                                           Handle<JSObject> object) {
663 664 665 666 667
  // Check access rights if required.
  if (object->IsAccessCheckNeeded() &&
      !isolate_->MayAccess(handle(isolate_->context()), object)) {
    // The cross-origin spec says that [[Enumerate]] shall return an empty
    // iterator when it doesn't have access...
668
    if (mode_ == KeyCollectionMode::kIncludePrototypes) {
669 670 671
      return Just(false);
    }
    // ...whereas [[OwnPropertyKeys]] shall return whitelisted properties.
672
    DCHECK(KeyCollectionMode::kOwnOnly == mode_);
673 674 675 676 677 678 679 680 681 682 683 684 685 686
    Handle<AccessCheckInfo> access_check_info;
    {
      DisallowHeapAllocation no_gc;
      AccessCheckInfo* maybe_info = AccessCheckInfo::Get(isolate_, object);
      if (maybe_info) access_check_info = handle(maybe_info, isolate_);
    }
    // We always have both kinds of interceptors or none.
    if (!access_check_info.is_null() &&
        access_check_info->named_interceptor()) {
      MAYBE_RETURN(CollectAccessCheckInterceptorKeys(access_check_info,
                                                     receiver, object),
                   Nothing<bool>());
      return Just(false);
    }
687 688
    filter_ = static_cast<PropertyFilter>(filter_ | ONLY_ALL_CAN_READ);
  }
689 690
  MAYBE_RETURN(CollectOwnElementIndices(receiver, object), Nothing<bool>());
  MAYBE_RETURN(CollectOwnPropertyNames(receiver, object), Nothing<bool>());
691 692 693 694
  return Just(true);
}

// static
695
Handle<FixedArray> KeyAccumulator::GetOwnEnumPropertyKeys(
696 697 698 699
    Isolate* isolate, Handle<JSObject> object) {
  if (object->HasFastProperties()) {
    return GetFastEnumPropertyKeys(isolate, object);
  } else if (object->IsJSGlobalObject()) {
700 701
    return GetOwnEnumPropertyDictionaryKeys(
        isolate, KeyCollectionMode::kOwnOnly, nullptr, object,
702
        JSGlobalObject::cast(*object)->global_dictionary());
703
  } else {
704 705 706
    return GetOwnEnumPropertyDictionaryKeys(
        isolate, KeyCollectionMode::kOwnOnly, nullptr, object,
        object->property_dictionary());
707 708 709
  }
}

710 711 712 713 714 715 716 717 718 719 720
namespace {

struct NameComparator {
  bool operator()(uint32_t hash1, uint32_t hash2, const Handle<Name>& key1,
                  const Handle<Name>& key2) const {
    return Name::Equals(key1, key2);
  }
};

}  // namespace

721 722
// ES6 9.5.12
// Returns |true| on success, |nothing| in case of exception.
723 724
Maybe<bool> KeyAccumulator::CollectOwnJSProxyKeys(Handle<JSReceiver> receiver,
                                                  Handle<JSProxy> proxy) {
725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743
  STACK_CHECK(isolate_, Nothing<bool>());
  // 1. Let handler be the value of the [[ProxyHandler]] internal slot of O.
  Handle<Object> handler(proxy->handler(), isolate_);
  // 2. If handler is null, throw a TypeError exception.
  // 3. Assert: Type(handler) is Object.
  if (proxy->IsRevoked()) {
    isolate_->Throw(*isolate_->factory()->NewTypeError(
        MessageTemplate::kProxyRevoked, isolate_->factory()->ownKeys_string()));
    return Nothing<bool>();
  }
  // 4. Let target be the value of the [[ProxyTarget]] internal slot of O.
  Handle<JSReceiver> target(proxy->target(), isolate_);
  // 5. Let trap be ? GetMethod(handler, "ownKeys").
  Handle<Object> trap;
  ASSIGN_RETURN_ON_EXCEPTION_VALUE(
      isolate_, trap, Object::GetMethod(Handle<JSReceiver>::cast(handler),
                                        isolate_->factory()->ownKeys_string()),
      Nothing<bool>());
  // 6. If trap is undefined, then
744
  if (trap->IsUndefined(isolate_)) {
745
    // 6a. Return target.[[OwnPropertyKeys]]().
746
    return CollectOwnJSProxyTargetKeys(proxy, target);
747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793
  }
  // 7. Let trapResultArray be Call(trap, handler, «target»).
  Handle<Object> trap_result_array;
  Handle<Object> args[] = {target};
  ASSIGN_RETURN_ON_EXCEPTION_VALUE(
      isolate_, trap_result_array,
      Execution::Call(isolate_, trap, handler, arraysize(args), args),
      Nothing<bool>());
  // 8. Let trapResult be ? CreateListFromArrayLike(trapResultArray,
  //    «String, Symbol»).
  Handle<FixedArray> trap_result;
  ASSIGN_RETURN_ON_EXCEPTION_VALUE(
      isolate_, trap_result,
      Object::CreateListFromArrayLike(isolate_, trap_result_array,
                                      ElementTypes::kStringAndSymbol),
      Nothing<bool>());
  // 9. Let extensibleTarget be ? IsExtensible(target).
  Maybe<bool> maybe_extensible = JSReceiver::IsExtensible(target);
  MAYBE_RETURN(maybe_extensible, Nothing<bool>());
  bool extensible_target = maybe_extensible.FromJust();
  // 10. Let targetKeys be ? target.[[OwnPropertyKeys]]().
  Handle<FixedArray> target_keys;
  ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate_, target_keys,
                                   JSReceiver::OwnPropertyKeys(target),
                                   Nothing<bool>());
  // 11. (Assert)
  // 12. Let targetConfigurableKeys be an empty List.
  // To save memory, we're re-using target_keys and will modify it in-place.
  Handle<FixedArray> target_configurable_keys = target_keys;
  // 13. Let targetNonconfigurableKeys be an empty List.
  Handle<FixedArray> target_nonconfigurable_keys =
      isolate_->factory()->NewFixedArray(target_keys->length());
  int nonconfigurable_keys_length = 0;
  // 14. Repeat, for each element key of targetKeys:
  for (int i = 0; i < target_keys->length(); ++i) {
    // 14a. Let desc be ? target.[[GetOwnProperty]](key).
    PropertyDescriptor desc;
    Maybe<bool> found = JSReceiver::GetOwnPropertyDescriptor(
        isolate_, target, handle(target_keys->get(i), isolate_), &desc);
    MAYBE_RETURN(found, Nothing<bool>());
    // 14b. If desc is not undefined and desc.[[Configurable]] is false, then
    if (found.FromJust() && !desc.configurable()) {
      // 14b i. Append key as an element of targetNonconfigurableKeys.
      target_nonconfigurable_keys->set(nonconfigurable_keys_length,
                                       target_keys->get(i));
      nonconfigurable_keys_length++;
      // The key was moved, null it out in the original list.
794
      target_keys->set(i, Smi::kZero);
795 796 797 798 799 800 801 802 803 804
    } else {
      // 14c. Else,
      // 14c i. Append key as an element of targetConfigurableKeys.
      // (No-op, just keep it in |target_keys|.)
    }
  }
  // 15. If extensibleTarget is true and targetNonconfigurableKeys is empty,
  //     then:
  if (extensible_target && nonconfigurable_keys_length == 0) {
    // 15a. Return trapResult.
805
    return AddKeysFromJSProxy(proxy, trap_result);
806 807
  }
  // 16. Let uncheckedResultKeys be a new List which is a copy of trapResult.
808
  Zone set_zone(isolate_->allocator(), ZONE_NAME);
809
  ZoneAllocationPolicy alloc(&set_zone);
810 811
  const int kPresent = 1;
  const int kGone = 0;
812 813 814 815
  base::TemplateHashMapImpl<Handle<Name>, int, NameComparator,
                            ZoneAllocationPolicy>
      unchecked_result_keys(ZoneHashMap::kDefaultHashMapCapacity,
                            NameComparator(), alloc);
816 817
  int unchecked_result_keys_size = 0;
  for (int i = 0; i < trap_result->length(); ++i) {
818 819 820 821
    Handle<Name> key(Name::cast(trap_result->get(i)), isolate_);
    auto entry = unchecked_result_keys.LookupOrInsert(key, key->Hash(), alloc);
    if (entry->value != kPresent) {
      entry->value = kPresent;
822 823 824 825 826
      unchecked_result_keys_size++;
    }
  }
  // 17. Repeat, for each key that is an element of targetNonconfigurableKeys:
  for (int i = 0; i < nonconfigurable_keys_length; ++i) {
827 828
    Object* raw_key = target_nonconfigurable_keys->get(i);
    Handle<Name> key(Name::cast(raw_key), isolate_);
829 830
    // 17a. If key is not an element of uncheckedResultKeys, throw a
    //      TypeError exception.
831 832
    auto found = unchecked_result_keys.Lookup(key, key->Hash());
    if (found == nullptr || found->value == kGone) {
833
      isolate_->Throw(*isolate_->factory()->NewTypeError(
834
          MessageTemplate::kProxyOwnKeysMissing, key));
835 836 837
      return Nothing<bool>();
    }
    // 17b. Remove key from uncheckedResultKeys.
838
    found->value = kGone;
839 840 841 842
    unchecked_result_keys_size--;
  }
  // 18. If extensibleTarget is true, return trapResult.
  if (extensible_target) {
843
    return AddKeysFromJSProxy(proxy, trap_result);
844 845 846
  }
  // 19. Repeat, for each key that is an element of targetConfigurableKeys:
  for (int i = 0; i < target_configurable_keys->length(); ++i) {
847 848 849
    Object* raw_key = target_configurable_keys->get(i);
    if (raw_key->IsSmi()) continue;  // Zapped entry, was nonconfigurable.
    Handle<Name> key(Name::cast(raw_key), isolate_);
850 851
    // 19a. If key is not an element of uncheckedResultKeys, throw a
    //      TypeError exception.
852 853
    auto found = unchecked_result_keys.Lookup(key, key->Hash());
    if (found == nullptr || found->value == kGone) {
854
      isolate_->Throw(*isolate_->factory()->NewTypeError(
855
          MessageTemplate::kProxyOwnKeysMissing, key));
856 857 858
      return Nothing<bool>();
    }
    // 19b. Remove key from uncheckedResultKeys.
859
    found->value = kGone;
860 861 862 863 864 865 866 867 868 869
    unchecked_result_keys_size--;
  }
  // 20. If uncheckedResultKeys is not empty, throw a TypeError exception.
  if (unchecked_result_keys_size != 0) {
    DCHECK_GT(unchecked_result_keys_size, 0);
    isolate_->Throw(*isolate_->factory()->NewTypeError(
        MessageTemplate::kProxyOwnKeysNonExtensible));
    return Nothing<bool>();
  }
  // 21. Return trapResult.
870 871 872 873 874 875 876 877
  return AddKeysFromJSProxy(proxy, trap_result);
}

Maybe<bool> KeyAccumulator::CollectOwnJSProxyTargetKeys(
    Handle<JSProxy> proxy, Handle<JSReceiver> target) {
  // TODO(cbruni): avoid creating another KeyAccumulator
  Handle<FixedArray> keys;
  ASSIGN_RETURN_ON_EXCEPTION_VALUE(
878 879
      isolate_, keys,
      KeyAccumulator::GetKeys(target, KeyCollectionMode::kOwnOnly, filter_,
880
                              GetKeysConversion::kConvertToString, is_for_in_),
881
      Nothing<bool>());
882 883
  Maybe<bool> result = AddKeysFromJSProxy(proxy, keys);
  return result;
884 885
}

886 887
}  // namespace internal
}  // namespace v8