Commit 2fee8a0f authored by jkummerow's avatar jkummerow Committed by Commit bot

[proxies] Implement [[Enumerate]] and [[OwnPropertyKeys]]

Both are integrated into JSReceiver::GetKeys().

For now, the implementation ignores Symbol/DONT_ENUM filtering.

BUG=v8:1543
LOG=n

Committed: https://crrev.com/42c6056e6f247724d14dc887f6619a6bf5867a97
Cr-Commit-Position: refs/heads/master@{#32384}

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

Cr-Commit-Position: refs/heads/master@{#32386}
parent 97def40d
...@@ -152,31 +152,30 @@ function DerivedKeysTrap() { ...@@ -152,31 +152,30 @@ function DerivedKeysTrap() {
return enumerableNames return enumerableNames
} }
function DerivedEnumerateTrap() { // Implements part of ES6 9.5.11 Proxy.[[Enumerate]]:
var names = this.getPropertyNames() // Call the trap, which should return an iterator, exhaust the iterator,
var enumerableNames = [] // and return an array containing the values.
for (var i = 0, count = 0; i < names.length; ++i) { function ProxyEnumerate(trap, handler, target) {
var name = names[i] // 7. Let trapResult be ? Call(trap, handler, «target»).
if (IS_SYMBOL(name)) continue var trap_result = %_Call(trap, handler, target);
var desc = this.getPropertyDescriptor(TO_STRING(name)) // 8. If Type(trapResult) is not Object, throw a TypeError exception.
if (!IS_UNDEFINED(desc)) { if (!IS_SPEC_OBJECT(trap_result)) {
if (!desc.configurable) { throw MakeTypeError(kProxyHandlerReturned, handler, "non-Object",
throw MakeTypeError(kProxyPropNotConfigurable, "enumerate");
this, name, "getPropertyDescriptor")
}
if (desc.enumerable) enumerableNames[count++] = names[i]
}
} }
return enumerableNames // 9. Return trapResult.
} var result = [];
for (var it = trap_result.next(); !it.done; it = trap_result.next()) {
function ProxyEnumerate(proxy) { var key = it.value;
var handler = %GetHandler(proxy) // Not yet spec'ed as of 2015-11-25, but will be spec'ed soon:
if (IS_UNDEFINED(handler.enumerate)) { // If the iterator returns a non-string value, throw a TypeError.
return %Apply(DerivedEnumerateTrap, handler, [], 0, 0) if (!IS_STRING(key)) {
} else { throw MakeTypeError(kProxyHandlerReturned, handler, "non-String",
return ToNameArray(handler.enumerate(), "enumerate", false) "enumerate-iterator");
}
result.push(key);
} }
return result;
} }
//------------------------------------------------------------------- //-------------------------------------------------------------------
......
...@@ -217,6 +217,18 @@ void KeyAccumulator::AddKeysFromProxy(Handle<JSObject> array_like) { ...@@ -217,6 +217,18 @@ void KeyAccumulator::AddKeysFromProxy(Handle<JSObject> array_like) {
} }
void KeyAccumulator::AddKeysFromProxy(Handle<FixedArray> keys) {
// Proxies define a complete list of keys with no distinction of
// elements and properties, which breaks the normal assumption for the
// KeyAccumulator.
AddKeys(keys, PROXY_MAGIC);
// Invert the current length to indicate a present proxy, so we can ignore
// element keys for this level. Otherwise we would not fully respect the order
// given by the proxy.
level_string_length_ = -level_string_length_;
}
void KeyAccumulator::AddElementKeysFromInterceptor( void KeyAccumulator::AddElementKeysFromInterceptor(
Handle<JSObject> array_like) { Handle<JSObject> array_like) {
AddKeys(array_like, CONVERT_TO_ARRAY_INDEX); AddKeys(array_like, CONVERT_TO_ARRAY_INDEX);
......
...@@ -44,6 +44,7 @@ class KeyAccumulator final BASE_EMBEDDED { ...@@ -44,6 +44,7 @@ class KeyAccumulator final BASE_EMBEDDED {
void AddKeys(Handle<JSObject> array, void AddKeys(Handle<JSObject> array,
AddKeyConversion convert = DO_NOT_CONVERT); AddKeyConversion convert = DO_NOT_CONVERT);
void AddKeysFromProxy(Handle<JSObject> array); void AddKeysFromProxy(Handle<JSObject> array);
void AddKeysFromProxy(Handle<FixedArray> keys);
void AddElementKeysFromInterceptor(Handle<JSObject> array); void AddElementKeysFromInterceptor(Handle<JSObject> array);
// Jump to the next level, pushing the current |levelLength_| to // Jump to the next level, pushing the current |levelLength_| to
// |levelLengths_| and adding a new list to |elements_|. // |levelLengths_| and adding a new list to |elements_|.
......
...@@ -149,6 +149,7 @@ class CallSite { ...@@ -149,6 +149,7 @@ class CallSite {
T(NotIntlObject, "% is not an i18n object.") \ T(NotIntlObject, "% is not an i18n object.") \
T(NotGeneric, "% is not generic") \ T(NotGeneric, "% is not generic") \
T(NotIterable, "% is not iterable") \ T(NotIterable, "% is not iterable") \
T(NotPropertyName, "% is not a valid property name") \
T(NotTypedArray, "this is not a typed array.") \ T(NotTypedArray, "this is not a typed array.") \
T(NotSharedTypedArray, "% is not a shared typed array.") \ T(NotSharedTypedArray, "% is not a shared typed array.") \
T(NotIntegerSharedTypedArray, "% is not an integer shared typed array.") \ T(NotIntegerSharedTypedArray, "% is not an integer shared typed array.") \
...@@ -206,6 +207,7 @@ class CallSite { ...@@ -206,6 +207,7 @@ class CallSite {
"Proxy target property '%' is not configurable") \ "Proxy target property '%' is not configurable") \
T(ProxyTrapFunctionExpected, \ T(ProxyTrapFunctionExpected, \
"Proxy.createFunction called with non-function for '%' trap") \ "Proxy.createFunction called with non-function for '%' trap") \
T(ProxyTrapResultMustInclude, "Trap result must include %.") \
T(RedefineDisallowed, "Cannot redefine property: %") \ T(RedefineDisallowed, "Cannot redefine property: %") \
T(RedefineExternalArray, \ T(RedefineExternalArray, \
"Cannot redefine a property of an object with external array elements") \ "Cannot redefine a property of an object with external array elements") \
......
This diff is collapsed.
...@@ -9528,6 +9528,16 @@ class JSProxy: public JSReceiver { ...@@ -9528,6 +9528,16 @@ class JSProxy: public JSReceiver {
MUST_USE_RESULT static Maybe<bool> DeletePropertyOrElement( MUST_USE_RESULT static Maybe<bool> DeletePropertyOrElement(
Handle<JSProxy> proxy, Handle<Name> name, LanguageMode language_mode); Handle<JSProxy> proxy, Handle<Name> name, LanguageMode language_mode);
// ES6 9.5.11
static bool Enumerate(Isolate* isolate, Handle<JSReceiver> receiver,
Handle<JSProxy> proxy, KeyAccumulator* accumulator);
// ES6 9.5.12
static bool OwnPropertyKeys(Isolate* isolate, Handle<JSReceiver> receiver,
Handle<JSProxy> proxy, KeyFilter filter,
Enumerability enum_policy,
KeyAccumulator* accumulator);
MUST_USE_RESULT static MaybeHandle<Object> GetPropertyWithHandler( MUST_USE_RESULT static MaybeHandle<Object> GetPropertyWithHandler(
Handle<JSProxy> proxy, Handle<JSProxy> proxy,
Handle<Object> receiver, Handle<Object> receiver,
......
// Copyright 2015 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: --harmony-proxies
var target = {
"target_one": 1
};
target.__proto__ = {
"target_two": 2
};
var handler = {
enumerate: function(target) {
function* keys() {
yield "foo";
yield "bar";
}
return keys();
}
}
var proxy = new Proxy(target, handler);
function TestForIn(receiver, expected) {
var result = [];
for (var k in receiver) {
result.push(k);
}
assertEquals(expected, result);
}
TestForIn(proxy, ["foo", "bar"]);
// Properly call traps on proxies on the prototype chain.
var receiver = {
"receiver_one": 1
};
receiver.__proto__ = proxy;
// TODO(jkummerow): Needs proper 'has' trap; implement that and enable this!
// TestForIn(receiver, ["receiver_one", "foo", "bar"]);
// Fall through to default behavior when trap is undefined.
handler.enumerate = undefined;
TestForIn(proxy, ["target_one", "target_two"]);
delete handler.enumerate;
TestForIn(proxy, ["target_one", "target_two"]);
// Non-string keys must be filtered.
function TestNonStringKey(key) {
handler.enumerate = function(target) {
function* keys() { yield key; }
return keys();
}
assertThrows("for (var k in proxy) {}", TypeError);
}
TestNonStringKey(1);
TestNonStringKey(3.14);
TestNonStringKey(Symbol("foo"));
TestNonStringKey({bad: "value"});
TestNonStringKey(null);
TestNonStringKey(undefined);
TestNonStringKey(true);
// Copyright 2015 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: --harmony-proxies --harmony-reflect
var target = {
"target_one": 1
};
target.__proto__ = {
"target_proto_two": 2
};
var handler = {
ownKeys: function(target) {
return ["foo", "bar"];
}
}
var proxy = new Proxy(target, handler);
// Simple case.
assertEquals(["foo", "bar"], Reflect.ownKeys(proxy));
// Test interesting steps of the algorithm:
// Step 6: Fall through to target.[[OwnPropertyKeys]] if the trap is undefined.
handler.ownKeys = undefined;
assertEquals(["target_one"], Reflect.ownKeys(proxy));
// Step 7: Throwing traps don't crash.
handler.ownKeys = function(target) { throw 1; };
assertThrows("Reflect.ownKeys(proxy)");
// Step 8: CreateListFromArrayLike error cases:
// Returning a non-Object throws.
var keys = 1;
handler.ownKeys = function(target) { return keys; };
assertThrows("Reflect.ownKeys(proxy)", TypeError);
keys = "string";
assertThrows("Reflect.ownKeys(proxy)", TypeError);
keys = Symbol("foo");
assertThrows("Reflect.ownKeys(proxy)", TypeError);
keys = null;
assertThrows("Reflect.ownKeys(proxy)", TypeError);
// "length" property is honored.
keys = { 0: "a", 1: "b", 2: "c" };
keys.length = 0;
assertEquals([], Reflect.ownKeys(proxy));
keys.length = 1;
assertEquals(["a"], Reflect.ownKeys(proxy));
keys.length = 3;
assertEquals(["a", "b", "c"], Reflect.ownKeys(proxy));
// The spec wants to allow lengths up to 2^53, but we can't allocate arrays
// of that size, so we throw even for smaller values.
keys.length = Math.pow(2, 33);
assertThrows("Reflect.ownKeys(proxy)", RangeError);
// Non-Name results throw.
keys = [1];
assertThrows("Reflect.ownKeys(proxy)", TypeError);
keys = [{}];
assertThrows("Reflect.ownKeys(proxy)", TypeError);
keys = [{toString: function() { return "foo"; }}];
assertThrows("Reflect.ownKeys(proxy)", TypeError);
keys = [null];
assertThrows("Reflect.ownKeys(proxy)", TypeError);
// Step 17a: The trap result must include all non-configurable keys.
Object.defineProperty(target, "nonconf", {value: 1, configurable: false});
keys = ["foo"];
assertThrows("Reflect.ownKeys(proxy)", TypeError);
keys = ["nonconf"];
assertEquals(keys, Reflect.ownKeys(proxy));
// Step 19a: The trap result must all keys of a non-extensible target.
Object.freeze(target);
assertThrows("Reflect.ownKeys(proxy)", TypeError);
keys = ["nonconf", "target_one"];
assertEquals(keys, Reflect.ownKeys(proxy));
// Step 20: The trap result must not add keys to a non-extensible target.
keys = ["nonconf", "target_one", "fantasy"];
assertThrows("Reflect.ownKeys(proxy)", TypeError);
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