Commit be9c5fd9 authored by Camillo Bruni's avatar Camillo Bruni Committed by Commit Bot

[elements] Fix Object.entries/values with changing elements

Drive-by-cleanup:
- Add InternalElementsAccessor to expose protected instance methods
  to ElementsAccessor subclasses.
- Make some more ElementsAccessor methods protected that take the
  raw entry as parameter.

Bug: chromium:798644
Change-Id: Iffd00f1953461e8dd22c123e62298410fb6e049c
Reviewed-on: https://chromium-review.googlesource.com/856816
Commit-Queue: Camillo Bruni <cbruni@chromium.org>
Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#50480}
parent 6dca60ed
...@@ -519,6 +519,21 @@ static Maybe<int64_t> IndexOfValueSlowPath(Isolate* isolate, ...@@ -519,6 +519,21 @@ static Maybe<int64_t> IndexOfValueSlowPath(Isolate* isolate,
return Just<int64_t>(-1); return Just<int64_t>(-1);
} }
// The InternalElementsAccessor is a helper class to expose otherwise protected
// methods to its subclasses. Namely, we don't want to publicly expose methods
// that take an entry (instead of an index) as an argument.
class InternalElementsAccessor : public ElementsAccessor {
public:
explicit InternalElementsAccessor(const char* name)
: ElementsAccessor(name) {}
virtual uint32_t GetEntryForIndex(Isolate* isolate, JSObject* holder,
FixedArrayBase* backing_store,
uint32_t index) = 0;
virtual PropertyDetails GetDetails(JSObject* holder, uint32_t entry) = 0;
};
// Base class for element handler implementations. Contains the // Base class for element handler implementations. Contains the
// the common logic for objects with different ElementsKinds. // the common logic for objects with different ElementsKinds.
// Subclasses must specialize method for which the element // Subclasses must specialize method for which the element
...@@ -537,10 +552,10 @@ static Maybe<int64_t> IndexOfValueSlowPath(Isolate* isolate, ...@@ -537,10 +552,10 @@ static Maybe<int64_t> IndexOfValueSlowPath(Isolate* isolate,
// CRTP to guarantee aggressive compile time optimizations (i.e. inlining and // CRTP to guarantee aggressive compile time optimizations (i.e. inlining and
// specialization of SomeElementsAccessor methods). // specialization of SomeElementsAccessor methods).
template <typename Subclass, typename ElementsTraitsParam> template <typename Subclass, typename ElementsTraitsParam>
class ElementsAccessorBase : public ElementsAccessor { class ElementsAccessorBase : public InternalElementsAccessor {
public: public:
explicit ElementsAccessorBase(const char* name) explicit ElementsAccessorBase(const char* name)
: ElementsAccessor(name) { } : InternalElementsAccessor(name) {}
typedef ElementsTraitsParam ElementsTraits; typedef ElementsTraitsParam ElementsTraits;
typedef typename ElementsTraitsParam::BackingStore BackingStore; typedef typename ElementsTraitsParam::BackingStore BackingStore;
...@@ -1052,35 +1067,65 @@ class ElementsAccessorBase : public ElementsAccessor { ...@@ -1052,35 +1067,65 @@ class ElementsAccessorBase : public ElementsAccessor {
Isolate* isolate, Handle<JSObject> object, Isolate* isolate, Handle<JSObject> object,
Handle<FixedArray> values_or_entries, bool get_entries, int* nof_items, Handle<FixedArray> values_or_entries, bool get_entries, int* nof_items,
PropertyFilter filter) { PropertyFilter filter) {
int count = 0; DCHECK_EQ(*nof_items, 0);
KeyAccumulator accumulator(isolate, KeyCollectionMode::kOwnOnly, KeyAccumulator accumulator(isolate, KeyCollectionMode::kOwnOnly,
ALL_PROPERTIES); ALL_PROPERTIES);
Subclass::CollectElementIndicesImpl( Subclass::CollectElementIndicesImpl(
object, handle(object->elements(), isolate), &accumulator); object, handle(object->elements(), isolate), &accumulator);
Handle<FixedArray> keys = accumulator.GetKeys(); Handle<FixedArray> keys = accumulator.GetKeys();
for (int i = 0; i < keys->length(); ++i) { int count = 0;
int i = 0;
Handle<Map> original_map(object->map(), isolate);
for (; i < keys->length(); ++i) {
Handle<Object> key(keys->get(i), isolate); Handle<Object> key(keys->get(i), isolate);
Handle<Object> value;
uint32_t index; uint32_t index;
if (!key->ToUint32(&index)) continue; if (!key->ToUint32(&index)) continue;
DCHECK_EQ(object->map(), *original_map);
uint32_t entry = Subclass::GetEntryForIndexImpl( uint32_t entry = Subclass::GetEntryForIndexImpl(
isolate, *object, object->elements(), index, filter); isolate, *object, object->elements(), index, filter);
if (entry == kMaxUInt32) continue; if (entry == kMaxUInt32) continue;
PropertyDetails details = Subclass::GetDetailsImpl(*object, entry); PropertyDetails details = Subclass::GetDetailsImpl(*object, entry);
Handle<Object> value;
if (details.kind() == kData) { if (details.kind() == kData) {
value = Subclass::GetImpl(isolate, object->elements(), entry); value = Subclass::GetImpl(isolate, object->elements(), entry);
} else { } else {
// This might modify the elements and/or change the elements kind.
LookupIterator it(isolate, object, index, LookupIterator::OWN); LookupIterator it(isolate, object, index, LookupIterator::OWN);
ASSIGN_RETURN_ON_EXCEPTION_VALUE( ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, value, Object::GetProperty(&it), Nothing<bool>()); isolate, value, Object::GetProperty(&it), Nothing<bool>());
} }
if (get_entries) { if (get_entries) value = MakeEntryPair(isolate, index, value);
value = MakeEntryPair(isolate, index, value); values_or_entries->set(count++, *value);
if (object->map() != *original_map) break;
}
// Slow path caused by changes in elements kind during iteration.
for (; i < keys->length(); i++) {
Handle<Object> key(keys->get(i), isolate);
uint32_t index;
if (!key->ToUint32(&index)) continue;
if (filter & ONLY_ENUMERABLE) {
InternalElementsAccessor* accessor =
reinterpret_cast<InternalElementsAccessor*>(
object->GetElementsAccessor());
uint32_t entry = accessor->GetEntryForIndex(isolate, *object,
object->elements(), index);
if (entry == kMaxUInt32) continue;
PropertyDetails details = accessor->GetDetails(*object, entry);
if (!details.IsEnumerable()) continue;
} }
Handle<Object> value;
LookupIterator it(isolate, object, index, LookupIterator::OWN);
ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, value, Object::GetProperty(&it),
Nothing<bool>());
if (get_entries) value = MakeEntryPair(isolate, index, value);
values_or_entries->set(count++, *value); values_or_entries->set(count++, *value);
} }
...@@ -1710,12 +1755,13 @@ class DictionaryElementsAccessor ...@@ -1710,12 +1755,13 @@ class DictionaryElementsAccessor
return result; return result;
} }
} }
Handle<Map> original_map(receiver->map(), isolate);
Handle<NumberDictionary> dictionary( Handle<NumberDictionary> dictionary(
NumberDictionary::cast(receiver->elements()), isolate); NumberDictionary::cast(receiver->elements()), isolate);
// Iterate through entire range, as accessing elements out of order is // Iterate through entire range, as accessing elements out of order is
// observable // observable
for (uint32_t k = start_from; k < length; ++k) { for (uint32_t k = start_from; k < length; ++k) {
DCHECK_EQ(receiver->map(), *original_map);
int entry = dictionary->FindEntry(isolate, k); int entry = dictionary->FindEntry(isolate, k);
if (entry == NumberDictionary::kNotFound) { if (entry == NumberDictionary::kNotFound) {
if (search_for_hole) return Just(true); if (search_for_hole) return Just(true);
...@@ -1780,15 +1826,15 @@ class DictionaryElementsAccessor ...@@ -1780,15 +1826,15 @@ class DictionaryElementsAccessor
uint32_t start_from, uint32_t length) { uint32_t start_from, uint32_t length) {
DCHECK(JSObject::PrototypeHasNoElements(isolate, *receiver)); DCHECK(JSObject::PrototypeHasNoElements(isolate, *receiver));
Handle<Map> original_map(receiver->map(), isolate);
Handle<NumberDictionary> dictionary( Handle<NumberDictionary> dictionary(
NumberDictionary::cast(receiver->elements()), isolate); NumberDictionary::cast(receiver->elements()), isolate);
// Iterate through entire range, as accessing elements out of order is // Iterate through entire range, as accessing elements out of order is
// observable. // observable.
for (uint32_t k = start_from; k < length; ++k) { for (uint32_t k = start_from; k < length; ++k) {
DCHECK_EQ(receiver->map(), *original_map);
int entry = dictionary->FindEntry(isolate, k); int entry = dictionary->FindEntry(isolate, k);
if (entry == NumberDictionary::kNotFound) { if (entry == NumberDictionary::kNotFound) continue;
continue;
}
PropertyDetails details = GetDetailsImpl(*dictionary, entry); PropertyDetails details = GetDetailsImpl(*dictionary, entry);
switch (details.kind()) { switch (details.kind()) {
...@@ -3698,12 +3744,13 @@ class SloppyArgumentsElementsAccessor ...@@ -3698,12 +3744,13 @@ class SloppyArgumentsElementsAccessor
Handle<Object> value, Handle<Object> value,
uint32_t start_from, uint32_t length) { uint32_t start_from, uint32_t length) {
DCHECK(JSObject::PrototypeHasNoElements(isolate, *object)); DCHECK(JSObject::PrototypeHasNoElements(isolate, *object));
Handle<Map> original_map = handle(object->map(), isolate); Handle<Map> original_map(object->map(), isolate);
Handle<SloppyArgumentsElements> elements( Handle<SloppyArgumentsElements> elements(
SloppyArgumentsElements::cast(object->elements()), isolate); SloppyArgumentsElements::cast(object->elements()), isolate);
bool search_for_hole = value->IsUndefined(isolate); bool search_for_hole = value->IsUndefined(isolate);
for (uint32_t k = start_from; k < length; ++k) { for (uint32_t k = start_from; k < length; ++k) {
DCHECK_EQ(object->map(), *original_map);
uint32_t entry = uint32_t entry =
GetEntryForIndexImpl(isolate, *object, *elements, k, ALL_PROPERTIES); GetEntryForIndexImpl(isolate, *object, *elements, k, ALL_PROPERTIES);
if (entry == kMaxUInt32) { if (entry == kMaxUInt32) {
...@@ -3739,11 +3786,12 @@ class SloppyArgumentsElementsAccessor ...@@ -3739,11 +3786,12 @@ class SloppyArgumentsElementsAccessor
Handle<Object> value, Handle<Object> value,
uint32_t start_from, uint32_t length) { uint32_t start_from, uint32_t length) {
DCHECK(JSObject::PrototypeHasNoElements(isolate, *object)); DCHECK(JSObject::PrototypeHasNoElements(isolate, *object));
Handle<Map> original_map = handle(object->map(), isolate); Handle<Map> original_map(object->map(), isolate);
Handle<SloppyArgumentsElements> elements( Handle<SloppyArgumentsElements> elements(
SloppyArgumentsElements::cast(object->elements()), isolate); SloppyArgumentsElements::cast(object->elements()), isolate);
for (uint32_t k = start_from; k < length; ++k) { for (uint32_t k = start_from; k < length; ++k) {
DCHECK_EQ(object->map(), *original_map);
uint32_t entry = uint32_t entry =
GetEntryForIndexImpl(isolate, *object, *elements, k, ALL_PROPERTIES); GetEntryForIndexImpl(isolate, *object, *elements, k, ALL_PROPERTIES);
if (entry == kMaxUInt32) { if (entry == kMaxUInt32) {
......
...@@ -54,9 +54,10 @@ class ElementsAccessor { ...@@ -54,9 +54,10 @@ class ElementsAccessor {
// typed array elements. // typed array elements.
virtual bool HasEntry(JSObject* holder, uint32_t entry) = 0; virtual bool HasEntry(JSObject* holder, uint32_t entry) = 0;
// TODO(cbruni): HasEntry and Get should not be exposed publicly with the
// entry parameter.
virtual Handle<Object> Get(Handle<JSObject> holder, uint32_t entry) = 0; virtual Handle<Object> Get(Handle<JSObject> holder, uint32_t entry) = 0;
virtual PropertyDetails GetDetails(JSObject* holder, uint32_t entry) = 0;
virtual bool HasAccessors(JSObject* holder) = 0; virtual bool HasAccessors(JSObject* holder) = 0;
virtual uint32_t NumberOfElements(JSObject* holder) = 0; virtual uint32_t NumberOfElements(JSObject* holder) = 0;
...@@ -67,8 +68,6 @@ class ElementsAccessor { ...@@ -67,8 +68,6 @@ class ElementsAccessor {
// element that is non-deletable. // element that is non-deletable.
virtual void SetLength(Handle<JSArray> holder, uint32_t new_length) = 0; virtual void SetLength(Handle<JSArray> holder, uint32_t new_length) = 0;
// Deletes an element in an object.
virtual void Delete(Handle<JSObject> holder, uint32_t entry) = 0;
// If kCopyToEnd is specified as the copy_size to CopyElements, it copies all // If kCopyToEnd is specified as the copy_size to CopyElements, it copies all
// of elements from source after source_start to the destination array. // of elements from source after source_start to the destination array.
...@@ -126,11 +125,6 @@ class ElementsAccessor { ...@@ -126,11 +125,6 @@ class ElementsAccessor {
virtual void Set(Handle<JSObject> holder, uint32_t entry, Object* value) = 0; virtual void Set(Handle<JSObject> holder, uint32_t entry, Object* value) = 0;
virtual void Reconfigure(Handle<JSObject> object,
Handle<FixedArrayBase> backing_store, uint32_t entry,
Handle<Object> value,
PropertyAttributes attributes) = 0;
virtual void Add(Handle<JSObject> object, uint32_t index, virtual void Add(Handle<JSObject> object, uint32_t index,
Handle<Object> value, PropertyAttributes attributes, Handle<Object> value, PropertyAttributes attributes,
uint32_t new_capacity) = 0; uint32_t new_capacity) = 0;
...@@ -214,6 +208,15 @@ class ElementsAccessor { ...@@ -214,6 +208,15 @@ class ElementsAccessor {
FixedArrayBase* backing_store, FixedArrayBase* backing_store,
uint32_t index) = 0; uint32_t index) = 0;
virtual PropertyDetails GetDetails(JSObject* holder, uint32_t entry) = 0;
virtual void Reconfigure(Handle<JSObject> object,
Handle<FixedArrayBase> backing_store, uint32_t entry,
Handle<Object> value,
PropertyAttributes attributes) = 0;
// Deletes an element in an object.
virtual void Delete(Handle<JSObject> holder, uint32_t entry) = 0;
// NOTE: this method violates the handlified function signature convention: // NOTE: this method violates the handlified function signature convention:
// raw pointer parameter |source_holder| in the function that allocates. // raw pointer parameter |source_holder| in the function that allocates.
// This is done intentionally to avoid ArrayConcat() builtin performance // This is done intentionally to avoid ArrayConcat() builtin performance
......
...@@ -284,8 +284,8 @@ TestMutateDuringEnumeration(); ...@@ -284,8 +284,8 @@ TestMutateDuringEnumeration();
HOLEY_DOUBLE_ELEMENTS: [ [, , NaN], [ ["2", NaN] ] ], HOLEY_DOUBLE_ELEMENTS: [ [, , NaN], [ ["2", NaN] ] ],
DICTIONARY_ELEMENTS: [ Object.defineProperties({ 10000: "world" }, { DICTIONARY_ELEMENTS: [ Object.defineProperties({ 10000: "world" }, {
100: { enumerable: true, value: "hello" }, 100: { enumerable: true, value: "hello", configurable: true},
99: { enumerable: false, value: "nope" } 99: { enumerable: false, value: "nope", configurable: true}
}), [ ["100", "hello"], ["10000", "world" ] ] ], }), [ ["100", "hello"], ["10000", "world" ] ] ],
FAST_SLOPPY_ARGUMENTS_ELEMENTS: [ FAST_SLOPPY_ARGUMENTS_ELEMENTS: [
fastSloppyArguments("a", "b", "c"), fastSloppyArguments("a", "b", "c"),
...@@ -298,17 +298,42 @@ TestMutateDuringEnumeration(); ...@@ -298,17 +298,42 @@ TestMutateDuringEnumeration();
[ ["0", "s"], ["1", "t"], ["2", "r"]] ], [ ["0", "s"], ["1", "t"], ["2", "r"]] ],
SLOW_STRING_WRAPPER_ELEMENTS: [ SLOW_STRING_WRAPPER_ELEMENTS: [
Object.defineProperties(new String("str"), { Object.defineProperties(new String("str"), {
10000: { enumerable: false, value: "X" }, 10000: { enumerable: false, value: "X", configurable: true},
9999: { enumerable: true, value: "Y" } 9999: { enumerable: true, value: "Y", configurable: true}
}), [["0", "s"], ["1", "t"], ["2", "r"], ["9999", "Y"]] ], }), [["0", "s"], ["1", "t"], ["2", "r"], ["9999", "Y"]] ],
}; };
for (let [kind, [object, expected]] of Object.entries(element_kinds)) { for (let [kind, [object, expected]] of Object.entries(element_kinds)) {
let result1 = Object.entries(object); let result1 = Object.entries(object);
%HeapObjectVerify(object);
%HeapObjectVerify(result1);
assertEquals(expected, result1, `fast Object.entries() with ${kind}`); assertEquals(expected, result1, `fast Object.entries() with ${kind}`);
let proxy = new Proxy(object, {}); let proxy = new Proxy(object, {});
let result2 = Object.entries(proxy); let result2 = Object.entries(proxy);
%HeapObjectVerify(result2);
assertEquals(result1, result2, `slow Object.entries() with ${kind}`); assertEquals(result1, result2, `slow Object.entries() with ${kind}`);
} }
function makeFastElements(array) {
// Remove all possible getters.
for (let k of Object.getOwnPropertyNames(this)) {
if (k == "length") continue;
delete this[k];
}
// Make the array large enough to trigger re-checking for compaction.
this[1000] = 1;
// Make the elements fast again.
Array.prototype.unshift.call(this, 1.1);
}
// Test that changing the elements kind is supported.
for (let [kind, [object, expected]] of Object.entries(element_kinds)) {
if (kind == "FAST_STRING_WRAPPER_ELEMENTS") break;
object.__defineGetter__(1, makeFastElements);
let result1 = Object.entries(object).toString();
%HeapObjectVerify(object);
%HeapObjectVerify(result1);
}
})(); })();
// Copyright 2018 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.
// Flags: --allow-natives-syntax
let arr = [];
// Make the array large enough to trigger re-checking for compaction.
arr[1000] = 0x1234;
arr.__defineGetter__(256, function () {
// Remove the getter so we can compact the array.
delete arr[256];
// Trigger compaction.
arr.unshift(1.1);
});
let results = Object.entries(arr);
%HeapObjectVerify(results);
%HeapObjectVerify(arr);
let str = results.toString();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment