Commit ed24dfe8 authored by cbruni's avatar cbruni Committed by Commit bot

[runtime] Do not use the enum-cache for keys retrieval.

Currently we fail to properly handle shadowed properties. If the
receiver defines a non-enumerable property that reappears on the
prototype as enumerable it incorrectly shows up in [[Enumerate]].
By extending the KeyAccumulator to track non-enumerable properties
we can now properly filter them out when seeing them further up in
the prototype-chain.

BUG=v8:705
LOG=y

Review URL: https://codereview.chromium.org/1608523002

Cr-Commit-Position: refs/heads/master@{#33405}
parent 654a63e1
......@@ -1153,7 +1153,12 @@ class DictionaryElementsAccessor
if (!AccessorInfo::cast(accessors)->all_can_read()) continue;
}
PropertyAttributes attr = details.attributes();
if ((attr & filter) != 0) continue;
if ((attr & filter) != 0) {
if (attr & PropertyAttributes::DONT_ENUM) {
keys->HideKey(index);
}
continue;
}
keys->AddKey(index);
}
......
......@@ -138,10 +138,11 @@ bool KeyAccumulator::AddKey(uint32_t key) { return AddIntegerKey(key); }
bool KeyAccumulator::AddIntegerKey(uint32_t key) {
if (IsKeyHidden(key)) return false;
// Make sure we do not add keys to a proxy-level (see AddKeysFromProxy).
// We mark proxy-levels with a negative length
DCHECK_LE(0, level_string_length_);
// Binary search over all but the last level. The last one might not be
// Binary search over all but the last level. The last level might not be
// sorted yet.
for (size_t i = 1; i < elements_.size(); i++) {
if (AccumulatorHasKey(elements_[i - 1], key)) return false;
......@@ -154,11 +155,10 @@ bool KeyAccumulator::AddIntegerKey(uint32_t key) {
bool KeyAccumulator::AddStringKey(Handle<Object> key,
AddKeyConversion convert) {
if (IsKeyHidden(key)) return false;
if (string_properties_.is_null()) {
string_properties_ = OrderedHashSet::Allocate(isolate_, 16);
}
// TODO(cbruni): remove this conversion once we throw the correct TypeError
// for non-string/symbol elements returned by proxies
if (convert == PROXY_MAGIC && key->IsNumber()) {
key = isolate_->factory()->NumberToString(key);
}
......@@ -175,6 +175,7 @@ bool KeyAccumulator::AddStringKey(Handle<Object> key,
bool KeyAccumulator::AddSymbolKey(Handle<Object> key) {
if (IsKeyHidden(key)) return false;
if (symbol_properties_.is_null()) {
symbol_properties_ = OrderedHashSet::Allocate(isolate_, 16);
}
......@@ -299,6 +300,9 @@ void KeyAccumulator::SortCurrentElementsList() {
}
void KeyAccumulator::Prepare() { NextPrototype(); }
void KeyAccumulator::NextPrototype() {
// Store the protoLength on the first call of this method.
if (!elements_.empty()) {
......@@ -311,5 +315,32 @@ void KeyAccumulator::NextPrototype() {
}
void KeyAccumulator::HideKey(uint32_t key) { hidden_element_keys_.insert(key); }
void KeyAccumulator::HideKey(Object* key) {
HideKey(Handle<Object>(key, isolate_));
}
void KeyAccumulator::HideKey(Handle<Object> key) {
if (hidden_keys_.is_null()) {
hidden_keys_ = OrderedHashSet::Allocate(isolate_, 8);
}
hidden_keys_ = OrderedHashSet::Add(hidden_keys_, key);
}
bool KeyAccumulator::IsKeyHidden(uint32_t key) {
return hidden_element_keys_.count(key);
}
bool KeyAccumulator::IsKeyHidden(Handle<Object> key) {
if (hidden_keys_.is_null()) return false;
return OrderedHashSet::HasKey(hidden_keys_, key);
}
} // namespace internal
} // namespace v8
......@@ -5,6 +5,8 @@
#ifndef V8_KEY_ACCUMULATOR_H_
#define V8_KEY_ACCUMULATOR_H_
#include <set>
#include "src/isolate.h"
#include "src/objects.h"
......@@ -45,6 +47,10 @@ class KeyAccumulator final BASE_EMBEDDED {
void AddKeysFromProxy(Handle<JSObject> array);
Maybe<bool> AddKeysFromProxy(Handle<JSProxy> proxy, Handle<FixedArray> keys);
void AddElementKeysFromInterceptor(Handle<JSObject> array);
void HideKey(uint32_t key);
void HideKey(Object* key);
void HideKey(Handle<Object> key);
void Prepare();
// Jump to the next level, pushing the current |levelLength_| to
// |levelLengths_| and adding a new list to |elements_|.
void NextPrototype();
......@@ -59,6 +65,8 @@ class KeyAccumulator final BASE_EMBEDDED {
bool AddStringKey(Handle<Object> key, AddKeyConversion convert);
bool AddSymbolKey(Handle<Object> array);
void SortCurrentElementsListRemoveDuplicates();
bool IsKeyHidden(Handle<Object> key);
bool IsKeyHidden(uint32_t key);
Isolate* isolate_;
PropertyFilter filter_;
......@@ -73,6 +81,11 @@ class KeyAccumulator final BASE_EMBEDDED {
// |symbol_properties_| contains the unique Symbol property keys for all
// levels in insertion order per level.
Handle<OrderedHashSet> symbol_properties_;
// |hidden_keys_| keeps track of the non-enumerable property keys to prevent
// them from being added to the accumulator if they reappear higher up in the
// prototype-chain.
Handle<OrderedHashSet> hidden_keys_;
std::set<uint32_t> hidden_element_keys_;
// |length_| keeps track of the total number of all element and property keys.
int length_ = 0;
// |levelLength_| keeps track of the number of String keys in the current
......
......@@ -250,7 +250,8 @@ Handle<Object> CallSite::GetMethodName() {
if (!current->IsJSObject()) break;
Handle<JSObject> current_obj = Handle<JSObject>::cast(current);
if (current_obj->IsAccessCheckNeeded()) break;
Handle<FixedArray> keys = JSObject::GetEnumPropertyKeys(current_obj, false);
Handle<FixedArray> keys =
JSObject::GetOwnEnumPropertyKeys(current_obj, false);
for (int i = 0; i < keys->length(); i++) {
HandleScope inner_scope(isolate_);
if (!keys->get(i)->IsName()) continue;
......
......@@ -8098,8 +8098,8 @@ MaybeHandle<JSObject> JSObjectWalkVisitor<ContextObject>::StructureWalk(
PropertyFilter filter = static_cast<PropertyFilter>(
ONLY_WRITABLE | ONLY_ENUMERABLE | ONLY_CONFIGURABLE);
KeyAccumulator accumulator(isolate, filter);
accumulator.NextPrototype();
copy->CollectOwnPropertyNames(&accumulator, filter);
accumulator.Prepare();
copy->CollectOwnPropertyKeys(&accumulator, filter);
Handle<FixedArray> names = accumulator.GetKeys();
for (int i = 0; i < names->length(); i++) {
DCHECK(names->get(i)->IsName());
......@@ -8438,10 +8438,9 @@ static Handle<FixedArray> ReduceFixedArrayTo(
namespace {
Handle<FixedArray> GetFastEnumPropertyKeys(Isolate* isolate,
Handle<JSObject> object,
Handle<FixedArray> GetFastEnumPropertyKeys(Isolate* isolate, JSObject* object,
bool cache_enum_length) {
Handle<Map> map(object->map());
Handle<Map> map(object->map(), isolate);
Handle<DescriptorArray> descs =
Handle<DescriptorArray>(map->instance_descriptors(), isolate);
int own_property_count = map->EnumLength();
......@@ -8515,30 +8514,13 @@ Handle<FixedArray> GetFastEnumPropertyKeys(Isolate* isolate,
} // namespace
Handle<FixedArray> JSObject::GetEnumPropertyKeys(Handle<JSObject> object,
bool cache_enum_length) {
Isolate* isolate = object->GetIsolate();
if (object->HasFastProperties()) {
return GetFastEnumPropertyKeys(isolate, object, cache_enum_length);
} else if (object->IsJSGlobalObject()) {
Handle<GlobalDictionary> dictionary(object->global_dictionary());
int length = dictionary->NumberOfEnumElements();
if (length == 0) {
return Handle<FixedArray>(isolate->heap()->empty_fixed_array());
}
Handle<FixedArray> storage = isolate->factory()->NewFixedArray(length);
dictionary->CopyEnumKeysTo(*storage);
return storage;
} else {
Handle<NameDictionary> dictionary(object->property_dictionary());
int length = dictionary->NumberOfEnumElements();
if (length == 0) {
return Handle<FixedArray>(isolate->heap()->empty_fixed_array());
}
Handle<FixedArray> storage = isolate->factory()->NewFixedArray(length);
dictionary->CopyEnumKeysTo(*storage);
return storage;
}
Handle<FixedArray> JSObject::GetOwnEnumPropertyKeys(Handle<JSObject> object,
bool cache_enum_length) {
PropertyFilter filter = PropertyFilter::ENUMERABLE_STRINGS;
KeyAccumulator keys(object->GetIsolate(), filter);
keys.Prepare();
object->CollectOwnPropertyKeys(&keys, filter);
return keys.GetKeys();
}
......@@ -8622,30 +8604,7 @@ static Maybe<bool> GetKeysFromJSObject(Isolate* isolate,
isolate, receiver, object, *filter, accumulator);
MAYBE_RETURN(success, Nothing<bool>());
if (*filter == ENUMERABLE_STRINGS) {
// We can cache the computed property keys if access checks are
// not needed and no interceptors are involved.
//
// We do not use the cache if the object has elements and
// therefore it does not make sense to cache the property names
// for arguments objects. Arguments objects will always have
// elements.
// Wrapped strings have elements, but don't have an elements
// array or dictionary. So the fast inline test for whether to
// use the cache says yes, so we should not create a cache.
Handle<JSFunction> arguments_function(
JSFunction::cast(isolate->sloppy_arguments_map()->GetConstructor()));
bool cache_enum_length =
((object->map()->GetConstructor() != *arguments_function) &&
!object->IsJSValue() && !object->IsAccessCheckNeeded() &&
!object->HasNamedInterceptor() && !object->HasIndexedInterceptor());
// Compute the property keys and cache them if possible.
Handle<FixedArray> enum_keys =
JSObject::GetEnumPropertyKeys(object, cache_enum_length);
accumulator->AddKeys(enum_keys);
} else {
object->CollectOwnPropertyNames(accumulator, *filter);
}
object->CollectOwnPropertyKeys(accumulator, *filter, type);
// Add the property keys from the interceptor.
success = GetKeysFromInterceptor<v8::GenericNamedPropertyEnumeratorCallback,
......@@ -16390,24 +16349,74 @@ void FixedArray::SortPairs(FixedArray* numbers, uint32_t len) {
}
void JSObject::CollectOwnPropertyNames(KeyAccumulator* keys,
PropertyFilter filter) {
if (HasFastProperties()) {
int real_size = map()->NumberOfOwnDescriptors();
Handle<DescriptorArray> descs(map()->instance_descriptors());
for (int i = 0; i < real_size; i++) {
PropertyDetails details = descs->GetDetails(i);
if ((details.attributes() & filter) != 0) continue;
if (filter & ONLY_ALL_CAN_READ) {
if (details.kind() != kAccessor) continue;
Object* accessors = descs->GetValue(i);
if (!accessors->IsAccessorInfo()) continue;
if (!AccessorInfo::cast(accessors)->all_can_read()) continue;
namespace {
void CollectEnumCacheTo(KeyAccumulator* keys, JSObject* object) {
// We can cache the computed property keys if access checks are
// not needed and no interceptors are involved.
//
// We do not use the cache if the object has elements and
// therefore it does not make sense to cache the property names
// for arguments objects. Arguments objects will always have
// elements.
// Wrapped strings have elements, but don't have an elements
// array or dictionary. So the fast inline test for whether to
// use the cache says yes, so we should not create a cache.
Isolate* isolate = keys->isolate();
Handle<JSFunction> arguments_function(
JSFunction::cast(isolate->sloppy_arguments_map()->GetConstructor()));
bool cache_enum_length =
((object->map()->GetConstructor() != *arguments_function) &&
!object->IsJSValue() && !object->IsAccessCheckNeeded() &&
!object->HasNamedInterceptor() && !object->HasIndexedInterceptor());
// Compute the property keys and cache them if possible.
Handle<FixedArray> enum_keys =
GetFastEnumPropertyKeys(isolate, object, cache_enum_length);
keys->AddKeys(enum_keys);
}
} // namespace
void JSObject::CollectFastPropertyKeysTo(KeyAccumulator* keys,
PropertyFilter filter,
JSReceiver::KeyCollectionType type) {
// Avoid using the enum cache if we have to walk the prototype chain due
// to shadowing properties.
if (filter == ENUMERABLE_STRINGS && type == JSReceiver::OWN_ONLY) {
CollectEnumCacheTo(keys, this);
return;
}
int real_size = map()->NumberOfOwnDescriptors();
Handle<DescriptorArray> descs(map()->instance_descriptors());
for (int i = 0; i < real_size; i++) {
PropertyDetails details = descs->GetDetails(i);
if ((details.attributes() & filter) != 0) {
if (type == JSReceiver::OWN_ONLY) continue;
if (details.attributes() & DONT_ENUM) {
keys->HideKey(descs->GetKey(i));
}
Name* key = descs->GetKey(i);
if (key->FilterKey(filter)) continue;
keys->AddKey(key);
continue;
}
if (filter & ONLY_ALL_CAN_READ) {
if (details.kind() != kAccessor) continue;
Object* accessors = descs->GetValue(i);
if (!accessors->IsAccessorInfo()) continue;
if (!AccessorInfo::cast(accessors)->all_can_read()) continue;
}
Name* key = descs->GetKey(i);
if (key->FilterKey(filter)) continue;
keys->AddKey(key);
}
}
void JSObject::CollectOwnPropertyKeys(KeyAccumulator* keys,
PropertyFilter filter,
JSReceiver::KeyCollectionType type) {
if (HasFastProperties()) {
CollectFastPropertyKeysTo(keys, filter, type);
} else if (IsJSGlobalObject()) {
GlobalDictionary::CollectKeysTo(handle(global_dictionary()), keys, filter);
} else {
......@@ -18441,20 +18450,27 @@ template <typename Derived, typename Shape, typename Key>
void Dictionary<Derived, Shape, Key>::CollectKeysTo(
Handle<Dictionary<Derived, Shape, Key> > dictionary, KeyAccumulator* keys,
PropertyFilter filter) {
if (dictionary->NumberOfElements() == 0) return;
int capacity = dictionary->Capacity();
Handle<FixedArray> array =
keys->isolate()->factory()->NewFixedArray(dictionary->NumberOfElements());
int array_size = 0;
std::vector<int> hidden_key_indices;
{
DisallowHeapAllocation no_gc;
Dictionary<Derived, Shape, Key>* raw_dict = *dictionary;
for (int i = 0; i < capacity; i++) {
Object* k = raw_dict->KeyAt(i);
if (!raw_dict->IsKey(k) || k->FilterKey(filter)) continue;
Object* key = raw_dict->KeyAt(i);
if (!raw_dict->IsKey(key) || key->FilterKey(filter)) continue;
if (raw_dict->IsDeleted(i)) continue;
PropertyDetails details = raw_dict->DetailsAt(i);
if ((details.attributes() & filter) != 0) continue;
if ((details.attributes() & filter) != 0) {
if (details.attributes() & DONT_ENUM) {
hidden_key_indices.push_back(i);
}
continue;
}
if (filter & ONLY_ALL_CAN_READ) {
if (details.kind() != kAccessor) continue;
Object* accessors = raw_dict->ValueAt(i);
......@@ -18471,7 +18487,9 @@ void Dictionary<Derived, Shape, Key>::CollectKeysTo(
Smi** start = reinterpret_cast<Smi**>(array->GetFirstElementAddress());
std::sort(start, start + array_size, cmp);
}
for (uint32_t i = 0; i < hidden_key_indices.size(); i++) {
keys->HideKey(dictionary->KeyAt(hidden_key_indices[i]));
}
for (int i = 0; i < array_size; i++) {
int index = Smi::cast(array->get(i))->value();
keys->AddKey(dictionary->KeyAt(index));
......
......@@ -2284,9 +2284,6 @@ class JSObject: public JSReceiver {
inline void SetInternalField(int index, Object* value);
inline void SetInternalField(int index, Smi* value);
void CollectOwnPropertyNames(KeyAccumulator* keys,
PropertyFilter filter = ALL_PROPERTIES);
// Returns the number of properties on this object filtering out properties
// with the specified attributes (ignoring interceptors).
// TODO(jkummerow): Deprecated, only used by Object.observe.
......@@ -2296,12 +2293,15 @@ class JSObject: public JSReceiver {
// TODO(jkummerow): Deprecated, only used by Object.observe.
int GetOwnElementKeys(FixedArray* storage, PropertyFilter filter);
void CollectOwnPropertyKeys(
KeyAccumulator* keys, PropertyFilter filter = ALL_PROPERTIES,
JSReceiver::KeyCollectionType type = JSReceiver::OWN_ONLY);
static void CollectOwnElementKeys(Handle<JSObject> object,
KeyAccumulator* keys,
PropertyFilter filter);
static Handle<FixedArray> GetEnumPropertyKeys(Handle<JSObject> object,
bool cache_result);
static Handle<FixedArray> GetOwnEnumPropertyKeys(Handle<JSObject> object,
bool cache_result);
// Returns a new map with all transitions dropped from the object's current
// map and the ElementsKind set.
......@@ -2555,6 +2555,9 @@ class JSObject: public JSReceiver {
Handle<JSObject> object, Handle<Object> value, bool from_javascript,
ShouldThrow should_throw);
void CollectFastPropertyKeysTo(KeyAccumulator* keys, PropertyFilter filter,
JSReceiver::KeyCollectionType type);
DISALLOW_IMPLICIT_CONSTRUCTORS(JSObject);
};
......
// Copyright 2016 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.
function allKeys(object) {
var keys = [];
for (var key in object) {
keys.push(key);
}
return keys;
}
var a = { a1: true, a2: true};
var b = { b1: true, b2: true};
a.__proto__ = b;
assertEquals(['a1', 'a2', 'b1', 'b2'], allKeys(a));
assertEquals(['b1', 'b2'], allKeys(b));
// Adding a non-enumerable property to either a or b shouldn't change
// the result.
var propertyDescriptor = {
enumerable: false,
configurable: true,
writable: true,
value: true
};
Object.defineProperty(a, 'a3', propertyDescriptor);
assertEquals(['a1', 'a2', 'b1', 'b2'], allKeys(a));
assertEquals(['b1', 'b2'], allKeys(b));
Object.defineProperty(b, 'b3', propertyDescriptor);
assertEquals(['a1', 'a2', 'b1', 'b2'], allKeys(a));
assertEquals(['b1', 'b2'], allKeys(b));
// A non-enumerable property shadows an enumerable version on the prototype
// chain.
b['a3'] = true;
assertEquals(['a1', 'a2', 'b1', 'b2'], allKeys(a));
assertEquals(['b1', 'b2', 'a3'], allKeys(b));
// Try the same with indexed-properties.
var aIndex = {0:true, 1:true};
var bIndex = {10:true, 11:true};
aIndex.__proto__= bIndex;
assertEquals(['0', '1', '10', '11'], allKeys(aIndex));
assertEquals(['10', '11'], allKeys(bIndex));
Object.defineProperty(aIndex, 2, propertyDescriptor);
Object.defineProperty(bIndex, 12, propertyDescriptor);
assertEquals(['0', '1', '10', '11'], allKeys(aIndex));
assertEquals(['10', '11'], allKeys(bIndex));
bIndex[2] = 2;
assertEquals(['0', '1', '10', '11'], allKeys(aIndex));
assertEquals(['2', '10', '11'], allKeys(bIndex));
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