[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:
neis 2015-12-07 03:01:16 -08:00 committed by Commit bot
parent ec37add662
commit 4e2c0dd7a9
11 changed files with 213 additions and 118 deletions

View File

@ -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);
}

View File

@ -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,
&current_desc);
if (isolate->has_pending_exception()) return Nothing<bool>();
if (owned) {
PropertyDescriptor desc =
PropertyDescriptor::IsAccessorDescriptor(&current_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.

View File

@ -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);

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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();

View File

@ -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)

View 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]);
})();

View File

@ -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],

View File

@ -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); });

View File

@ -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`;");