[proxies] Make Object.{freeze,seal} behave correctly for proxies.
- Add JSReceiver::SetIntegrityLevel, with a fast path for regular objects. - Make Object.{freeze,seal} call this via %Object{Freeze,Seal}, thus no longer using broken or deprecated functions from v8natives.js. - Add JSReceiver::OwnPropertyKeys convenience function. - Reenable harmony/proxies-hash.js test. R=rossberg BUG=v8:1543 LOG=N Review URL: https://codereview.chromium.org/1489423002 Cr-Commit-Position: refs/heads/master@{#32651}
This commit is contained in:
parent
ec37add662
commit
4e2c0dd7a9
@ -920,54 +920,14 @@ function ObjectDefineProperties(obj, properties) {
|
||||
// ES5 section 15.2.3.8.
|
||||
function ObjectSealJS(obj) {
|
||||
if (!IS_SPEC_OBJECT(obj)) return obj;
|
||||
var isProxy = %_IsJSProxy(obj);
|
||||
if (isProxy || %HasSloppyArgumentsElements(obj) || %IsObserved(obj)) {
|
||||
// TODO(neis): For proxies, must call preventExtensions trap first.
|
||||
var names = OwnPropertyKeys(obj);
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
var name = names[i];
|
||||
var desc = GetOwnPropertyJS(obj, name);
|
||||
if (desc.isConfigurable()) {
|
||||
desc.setConfigurable(false);
|
||||
DefineOwnProperty(obj, name, desc, true);
|
||||
}
|
||||
}
|
||||
%PreventExtensions(obj);
|
||||
} else {
|
||||
// TODO(adamk): Is it worth going to this fast path if the
|
||||
// object's properties are already in dictionary mode?
|
||||
%ObjectSeal(obj);
|
||||
}
|
||||
return obj;
|
||||
return %ObjectSeal(obj);
|
||||
}
|
||||
|
||||
|
||||
// ES5 section 15.2.3.9.
|
||||
function ObjectFreezeJS(obj) {
|
||||
if (!IS_SPEC_OBJECT(obj)) return obj;
|
||||
var isProxy = %_IsJSProxy(obj);
|
||||
// TODO(conradw): Investigate modifying the fast path to accommodate strong
|
||||
// objects.
|
||||
if (isProxy || %HasSloppyArgumentsElements(obj) || %IsObserved(obj) ||
|
||||
IS_STRONG(obj)) {
|
||||
// TODO(neis): For proxies, must call preventExtensions trap first.
|
||||
var names = OwnPropertyKeys(obj);
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
var name = names[i];
|
||||
var desc = GetOwnPropertyJS(obj, name);
|
||||
if (desc.isWritable() || desc.isConfigurable()) {
|
||||
if (IsDataDescriptor(desc)) desc.setWritable(false);
|
||||
desc.setConfigurable(false);
|
||||
DefineOwnProperty(obj, name, desc, true);
|
||||
}
|
||||
}
|
||||
%PreventExtensions(obj);
|
||||
} else {
|
||||
// TODO(adamk): Is it worth going to this fast path if the
|
||||
// object's properties are already in dictionary mode?
|
||||
%ObjectFreeze(obj);
|
||||
}
|
||||
return obj;
|
||||
return %ObjectFreeze(obj);
|
||||
}
|
||||
|
||||
|
||||
|
@ -7233,6 +7233,70 @@ bool JSObject::ReferencesObject(Object* obj) {
|
||||
}
|
||||
|
||||
|
||||
Maybe<bool> JSReceiver::SetIntegrityLevel(Handle<JSReceiver> receiver,
|
||||
IntegrityLevel level,
|
||||
ShouldThrow should_throw) {
|
||||
DCHECK(level == SEALED || level == FROZEN);
|
||||
|
||||
if (receiver->IsJSObject()) {
|
||||
Handle<JSObject> object = Handle<JSObject>::cast(receiver);
|
||||
if (!object->HasSloppyArgumentsElements() &&
|
||||
!object->map()->is_observed() &&
|
||||
(!object->map()->is_strong() || level == SEALED)) { // Fast path.
|
||||
if (level == SEALED) {
|
||||
return JSObject::PreventExtensionsWithTransition<SEALED>(object,
|
||||
should_throw);
|
||||
} else {
|
||||
return JSObject::PreventExtensionsWithTransition<FROZEN>(object,
|
||||
should_throw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Isolate* isolate = receiver->GetIsolate();
|
||||
|
||||
MAYBE_RETURN(JSReceiver::PreventExtensions(receiver, should_throw),
|
||||
Nothing<bool>());
|
||||
|
||||
Handle<FixedArray> keys;
|
||||
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
|
||||
isolate, keys, JSReceiver::OwnPropertyKeys(receiver), Nothing<bool>());
|
||||
|
||||
PropertyDescriptor no_conf;
|
||||
no_conf.set_configurable(false);
|
||||
|
||||
PropertyDescriptor no_conf_no_write;
|
||||
no_conf_no_write.set_configurable(false);
|
||||
no_conf_no_write.set_writable(false);
|
||||
|
||||
if (level == SEALED) {
|
||||
for (int i = 0; i < keys->length(); ++i) {
|
||||
Handle<Object> key(keys->get(i), isolate);
|
||||
DefineOwnProperty(isolate, receiver, key, &no_conf, THROW_ON_ERROR);
|
||||
if (isolate->has_pending_exception()) return Nothing<bool>();
|
||||
}
|
||||
return Just(true);
|
||||
}
|
||||
|
||||
for (int i = 0; i < keys->length(); ++i) {
|
||||
Handle<Object> key(keys->get(i), isolate);
|
||||
PropertyDescriptor current_desc;
|
||||
bool owned = JSReceiver::GetOwnPropertyDescriptor(isolate, receiver, key,
|
||||
¤t_desc);
|
||||
if (isolate->has_pending_exception()) return Nothing<bool>();
|
||||
if (owned) {
|
||||
PropertyDescriptor desc =
|
||||
PropertyDescriptor::IsAccessorDescriptor(¤t_desc)
|
||||
? no_conf
|
||||
: no_conf_no_write;
|
||||
DefineOwnProperty(isolate, receiver, key, &desc, THROW_ON_ERROR);
|
||||
if (isolate->has_pending_exception()) return Nothing<bool>();
|
||||
}
|
||||
}
|
||||
return Just(true);
|
||||
}
|
||||
|
||||
|
||||
Maybe<bool> JSReceiver::PreventExtensions(Handle<JSReceiver> object,
|
||||
ShouldThrow should_throw) {
|
||||
if (object->IsJSProxy()) {
|
||||
@ -7555,20 +7619,6 @@ Maybe<bool> JSObject::PreventExtensionsWithTransition(
|
||||
}
|
||||
|
||||
|
||||
MaybeHandle<Object> JSObject::Freeze(Handle<JSObject> object) {
|
||||
MAYBE_RETURN_NULL(
|
||||
PreventExtensionsWithTransition<FROZEN>(object, THROW_ON_ERROR));
|
||||
return object;
|
||||
}
|
||||
|
||||
|
||||
MaybeHandle<Object> JSObject::Seal(Handle<JSObject> object) {
|
||||
MAYBE_RETURN_NULL(
|
||||
PreventExtensionsWithTransition<SEALED>(object, THROW_ON_ERROR));
|
||||
return object;
|
||||
}
|
||||
|
||||
|
||||
void JSObject::SetObserved(Handle<JSObject> object) {
|
||||
DCHECK(!object->IsJSGlobalProxy());
|
||||
DCHECK(!object->IsJSGlobalObject());
|
||||
@ -8409,11 +8459,8 @@ bool JSProxy::OwnPropertyKeys(Isolate* isolate, Handle<JSReceiver> receiver,
|
||||
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::GetKeys(target, JSReceiver::OWN_ONLY, ALL_PROPERTIES,
|
||||
CONVERT_TO_STRING),
|
||||
false);
|
||||
ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, target_keys,
|
||||
JSReceiver::OwnPropertyKeys(target), false);
|
||||
// 11. (Assert)
|
||||
// 12. Let targetConfigurableKeys be an empty List.
|
||||
// To save memory, we're re-using target_keys and will modify it in-place.
|
||||
|
@ -1864,8 +1864,14 @@ class JSReceiver: public HeapObject {
|
||||
static bool GetOwnPropertyDescriptor(LookupIterator* it,
|
||||
PropertyDescriptor* desc);
|
||||
|
||||
// Disallow further properties to be added to the object. This is
|
||||
// ES6's [[PreventExtensions]] when passed DONT_THROW.
|
||||
typedef PropertyAttributes IntegrityLevel;
|
||||
|
||||
// ES6 7.3.14 (when passed DONT_THROW)
|
||||
// 'level' must be SEALED or FROZEN.
|
||||
MUST_USE_RESULT static Maybe<bool> SetIntegrityLevel(
|
||||
Handle<JSReceiver> object, IntegrityLevel lvl, ShouldThrow should_throw);
|
||||
|
||||
// ES6 [[PreventExtensions]] (when passed DONT_THROW)
|
||||
MUST_USE_RESULT static Maybe<bool> PreventExtensions(
|
||||
Handle<JSReceiver> object, ShouldThrow should_throw);
|
||||
|
||||
@ -1919,6 +1925,13 @@ class JSReceiver: public HeapObject {
|
||||
|
||||
enum KeyCollectionType { OWN_ONLY, INCLUDE_PROTOS };
|
||||
|
||||
// ES6 [[OwnPropertyKeys]] (modulo return type)
|
||||
MUST_USE_RESULT static MaybeHandle<FixedArray> OwnPropertyKeys(
|
||||
Handle<JSReceiver> object) {
|
||||
return GetKeys(object, JSReceiver::OWN_ONLY, ALL_PROPERTIES,
|
||||
CONVERT_TO_STRING);
|
||||
}
|
||||
|
||||
// Computes the enumerable keys for a JSObject. Used for implementing
|
||||
// "for (n in object) { }".
|
||||
MUST_USE_RESULT static MaybeHandle<FixedArray> GetKeys(
|
||||
@ -2342,12 +2355,6 @@ class JSObject: public JSReceiver {
|
||||
|
||||
static bool IsExtensible(Handle<JSObject> object);
|
||||
|
||||
// ES5 Object.seal
|
||||
MUST_USE_RESULT static MaybeHandle<Object> Seal(Handle<JSObject> object);
|
||||
|
||||
// ES5 Object.freeze
|
||||
MUST_USE_RESULT static MaybeHandle<Object> Freeze(Handle<JSObject> object);
|
||||
|
||||
// Called the first time an object is observed with ES7 Object.observe.
|
||||
static void SetObserved(Handle<JSObject> object);
|
||||
|
||||
|
@ -234,11 +234,12 @@ RUNTIME_FUNCTION(Runtime_FinalizeClassDefinition) {
|
||||
|
||||
if (constructor->map()->is_strong()) {
|
||||
DCHECK(prototype->map()->is_strong());
|
||||
RETURN_FAILURE_ON_EXCEPTION(isolate, JSObject::Freeze(prototype));
|
||||
Handle<Object> result;
|
||||
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, result,
|
||||
JSObject::Freeze(constructor));
|
||||
return *result;
|
||||
MAYBE_RETURN(JSReceiver::SetIntegrityLevel(prototype, FROZEN,
|
||||
Object::THROW_ON_ERROR),
|
||||
isolate->heap()->exception());
|
||||
MAYBE_RETURN(JSReceiver::SetIntegrityLevel(constructor, FROZEN,
|
||||
Object::THROW_ON_ERROR),
|
||||
isolate->heap()->exception());
|
||||
}
|
||||
return *constructor;
|
||||
}
|
||||
|
@ -334,30 +334,24 @@ RUNTIME_FUNCTION(Runtime_OptimizeObjectForAddingMultipleProperties) {
|
||||
RUNTIME_FUNCTION(Runtime_ObjectFreeze) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK(args.length() == 1);
|
||||
CONVERT_ARG_HANDLE_CHECKED(JSObject, object, 0);
|
||||
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, object, 0);
|
||||
|
||||
// %ObjectFreeze is a fast path and these cases are handled elsewhere.
|
||||
RUNTIME_ASSERT(!object->HasSloppyArgumentsElements() &&
|
||||
!object->map()->is_observed() && !object->IsJSProxy());
|
||||
|
||||
Handle<Object> result;
|
||||
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, result, JSObject::Freeze(object));
|
||||
return *result;
|
||||
MAYBE_RETURN(
|
||||
JSReceiver::SetIntegrityLevel(object, FROZEN, Object::THROW_ON_ERROR),
|
||||
isolate->heap()->exception());
|
||||
return *object;
|
||||
}
|
||||
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_ObjectSeal) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK(args.length() == 1);
|
||||
CONVERT_ARG_HANDLE_CHECKED(JSObject, object, 0);
|
||||
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, object, 0);
|
||||
|
||||
// %ObjectSeal is a fast path and these cases are handled elsewhere.
|
||||
RUNTIME_ASSERT(!object->HasSloppyArgumentsElements() &&
|
||||
!object->map()->is_observed() && !object->IsJSProxy());
|
||||
|
||||
Handle<Object> result;
|
||||
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, result, JSObject::Seal(object));
|
||||
return *result;
|
||||
MAYBE_RETURN(
|
||||
JSReceiver::SetIntegrityLevel(object, SEALED, Object::THROW_ON_ERROR),
|
||||
isolate->heap()->exception());
|
||||
return *object;
|
||||
}
|
||||
|
||||
|
||||
|
@ -350,10 +350,10 @@ Object.freeze(obj);
|
||||
|
||||
Object.deliverChangeRecords(observer.callback);
|
||||
observer.assertCallbackRecords([
|
||||
{ object: obj, type: 'preventExtensions' },
|
||||
{ object: obj, type: 'reconfigure', name: 'a' },
|
||||
{ object: obj, type: 'reconfigure', name: 'b' },
|
||||
{ object: obj, type: 'reconfigure', name: 'c' },
|
||||
{ object: obj, type: 'preventExtensions' },
|
||||
]);
|
||||
|
||||
reset();
|
||||
@ -387,9 +387,9 @@ Object.seal(obj);
|
||||
|
||||
Object.deliverChangeRecords(observer.callback);
|
||||
observer.assertCallbackRecords([
|
||||
{ object: obj, type: 'preventExtensions' },
|
||||
{ object: obj, type: 'reconfigure', name: 'a' },
|
||||
{ object: obj, type: 'reconfigure', name: 'b' },
|
||||
{ object: obj, type: 'preventExtensions' },
|
||||
]);
|
||||
|
||||
reset();
|
||||
|
@ -32,9 +32,10 @@
|
||||
|
||||
function TestWithProxies(test, construct, handler) {
|
||||
test(construct, handler, function(h) { return new Proxy({}, h) })
|
||||
test(construct, handler, function(h) {
|
||||
return Proxy.createFunction(h, function() {})
|
||||
})
|
||||
// TODO(cbruni): Adapt and enable once we have [[Call]] working.
|
||||
// test(construct, handler, function(h) {
|
||||
// return Proxy.createFunction(h, function() {})
|
||||
// })
|
||||
}
|
||||
|
||||
|
||||
@ -70,10 +71,9 @@ function TestSet2(construct, fix, create) {
|
||||
assertFalse(s.has(p3));
|
||||
}
|
||||
|
||||
// TODO(neis): Reenable once proxies properly support these operations.
|
||||
// TestSet(Set, Object.seal)
|
||||
// TestSet(Set, Object.freeze)
|
||||
// TestSet(Set, Object.preventExtensions)
|
||||
TestSet(Set, Object.seal)
|
||||
TestSet(Set, Object.freeze)
|
||||
TestSet(Set, Object.preventExtensions)
|
||||
|
||||
|
||||
// Maps and weak maps.
|
||||
@ -114,12 +114,10 @@ function TestMap2(construct, fix, create) {
|
||||
assertSame(undefined, m.get(p2));
|
||||
}
|
||||
|
||||
// TODO(neis): Reenable once proxies properly support these operations.
|
||||
// TestMap(Map, Object.seal)
|
||||
// TestMap(Map, Object.freeze)
|
||||
// TestMap(Map, Object.preventExtensions)
|
||||
TestMap(Map, Object.seal)
|
||||
TestMap(Map, Object.freeze)
|
||||
TestMap(Map, Object.preventExtensions)
|
||||
|
||||
// TODO(neis): Reenable once proxies properly support these operations.
|
||||
// TestMap(WeakMap, Object.seal)
|
||||
// TestMap(WeakMap, Object.freeze)
|
||||
// TestMap(WeakMap, Object.preventExtensions)
|
||||
TestMap(WeakMap, Object.seal)
|
||||
TestMap(WeakMap, Object.freeze)
|
||||
TestMap(WeakMap, Object.preventExtensions)
|
||||
|
95
test/mjsunit/harmony/proxies-integrity.js
Normal file
95
test/mjsunit/harmony/proxies-integrity.js
Normal file
@ -0,0 +1,95 @@
|
||||
// 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
|
||||
|
||||
|
||||
|
||||
function toKey(x) {
|
||||
if (typeof x === "symbol") return x;
|
||||
return String(x);
|
||||
}
|
||||
|
||||
|
||||
const noconf = {configurable: false};
|
||||
const noconf_nowrite = {configurable: false, writable: false};
|
||||
|
||||
|
||||
var symbol = Symbol();
|
||||
|
||||
|
||||
var log = [];
|
||||
var logger = {};
|
||||
var handler = new Proxy({}, logger);
|
||||
|
||||
logger.get = function(t, trap, r) {
|
||||
return function() {
|
||||
log.push([trap, ...arguments]);
|
||||
return Reflect[trap](...arguments);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
(function Seal() {
|
||||
var target = [];
|
||||
var proxy = new Proxy(target, handler);
|
||||
log.length = 0;
|
||||
|
||||
target.wurst = 42;
|
||||
target[0] = true;
|
||||
Object.defineProperty(target, symbol, {get: undefined});
|
||||
|
||||
Object.seal(proxy);
|
||||
assertEquals(6, log.length)
|
||||
for (var i in log) assertSame(target, log[i][1]);
|
||||
|
||||
assertArrayEquals(
|
||||
["preventExtensions", target], log[0]);
|
||||
assertArrayEquals(
|
||||
["ownKeys", target], log[1]);
|
||||
assertArrayEquals(
|
||||
["defineProperty", target, toKey(0), noconf], log[2]);
|
||||
assertArrayEquals(
|
||||
["defineProperty", target, toKey("length"), noconf], log[3]);
|
||||
assertArrayEquals(
|
||||
["defineProperty", target, toKey("wurst"), noconf], log[4]);
|
||||
assertArrayEquals(
|
||||
["defineProperty", target, toKey(symbol), noconf], log[5]);
|
||||
})();
|
||||
|
||||
|
||||
(function Freeze() {
|
||||
var target = [];
|
||||
var proxy = new Proxy(target, handler);
|
||||
log.length = 0;
|
||||
|
||||
target.wurst = 42;
|
||||
target[0] = true;
|
||||
Object.defineProperty(target, symbol, {get: undefined});
|
||||
|
||||
Object.freeze(proxy);
|
||||
assertEquals(10, log.length)
|
||||
for (var i in log) assertSame(target, log[i][1]);
|
||||
|
||||
assertArrayEquals(
|
||||
["preventExtensions", target], log[0]);
|
||||
assertArrayEquals(
|
||||
["ownKeys", target], log[1]);
|
||||
assertArrayEquals(
|
||||
["getOwnPropertyDescriptor", target, toKey(0)], log[2]);
|
||||
assertArrayEquals(
|
||||
["defineProperty", target, toKey(0), noconf_nowrite], log[3]);
|
||||
assertArrayEquals(
|
||||
["getOwnPropertyDescriptor", target, toKey("length")], log[4]);
|
||||
assertArrayEquals(
|
||||
["defineProperty", target, toKey("length"), noconf_nowrite], log[5]);
|
||||
assertArrayEquals(
|
||||
["getOwnPropertyDescriptor", target, toKey("wurst")], log[6]);
|
||||
assertArrayEquals(
|
||||
["defineProperty", target, toKey("wurst"), noconf_nowrite], log[7]);
|
||||
assertArrayEquals(
|
||||
["getOwnPropertyDescriptor", target, toKey(symbol)], log[8]);
|
||||
assertArrayEquals(
|
||||
["defineProperty", target, toKey(symbol), noconf], log[9]);
|
||||
})();
|
@ -91,7 +91,6 @@
|
||||
'es6/regress/regress-cr493566': [SKIP],
|
||||
'for-in-opt': [SKIP],
|
||||
'harmony/proxies-example-membrane': [SKIP],
|
||||
'harmony/proxies-hash': [SKIP],
|
||||
'harmony/proxies-json': [SKIP],
|
||||
'harmony/proxies': [SKIP],
|
||||
'harmony/proxies-symbols': [SKIP],
|
||||
|
@ -1,9 +0,0 @@
|
||||
// Copyright 2014 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
|
||||
|
||||
function foo(a,b,c) { return arguments; }
|
||||
var f = foo(false, null, 40);
|
||||
assertThrows(function() { %ObjectFreeze(f); });
|
@ -14,6 +14,9 @@ assertThrows("'use strong'; eval('function f() {}');", SyntaxError);
|
||||
assertThrows("'use strong'; function f() {eval()}", SyntaxError);
|
||||
|
||||
assertDoesNotThrow("'use strong'; eval;");
|
||||
assertDoesNotThrow("'use strong'; eval`foo`;");
|
||||
assertDoesNotThrow("'use strong'; let foo = eval; foo();");
|
||||
assertDoesNotThrow("'use strong'; (1, eval)();");
|
||||
|
||||
// TODO(neis): The tagged template triggers %ObjectFreeze on an array, which
|
||||
// throws when trying to redefine 'length'.
|
||||
// assertDoesNotThrow("'use strong'; eval`foo`;");
|
||||
|
Loading…
Reference in New Issue
Block a user