Implement sealing, freezing, and related functions for proxies.
R=ager@chromium.org BUG=v8:1543 TEST= Review URL: http://codereview.chromium.org/7391001 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@8673 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
parent
ce75f236f4
commit
5e62e325ac
@ -892,6 +892,13 @@ Handle<JSProxy> Factory::NewJSProxy(Handle<Object> handler,
|
||||
}
|
||||
|
||||
|
||||
void Factory::BecomeJSObject(Handle<JSProxy> object) {
|
||||
CALL_HEAP_FUNCTION_VOID(
|
||||
isolate(),
|
||||
isolate()->heap()->ReinitializeJSProxyAsJSObject(*object));
|
||||
}
|
||||
|
||||
|
||||
Handle<SharedFunctionInfo> Factory::NewSharedFunctionInfo(
|
||||
Handle<String> name,
|
||||
int number_of_literals,
|
||||
|
@ -253,6 +253,9 @@ class Factory {
|
||||
|
||||
Handle<JSProxy> NewJSProxy(Handle<Object> handler, Handle<Object> prototype);
|
||||
|
||||
// Change the type of the argument into a regular JS object and reinitialize.
|
||||
void BecomeJSObject(Handle<JSProxy> object);
|
||||
|
||||
Handle<JSFunction> NewFunction(Handle<String> name,
|
||||
Handle<Object> prototype);
|
||||
|
||||
|
33
src/heap.cc
33
src/heap.cc
@ -3267,14 +3267,13 @@ MaybeObject* Heap::AllocateJSProxy(Object* handler, Object* prototype) {
|
||||
MaybeObject* maybe_map_obj = AllocateMap(JS_PROXY_TYPE, JSProxy::kSize);
|
||||
if (!maybe_map_obj->To<Map>(&map)) return maybe_map_obj;
|
||||
map->set_prototype(prototype);
|
||||
map->set_pre_allocated_property_fields(1);
|
||||
map->set_inobject_properties(1);
|
||||
|
||||
// Allocate the proxy object.
|
||||
Object* result;
|
||||
MaybeObject* maybe_result = Allocate(map, NEW_SPACE);
|
||||
if (!maybe_result->ToObject(&result)) return maybe_result;
|
||||
JSProxy::cast(result)->set_handler(handler);
|
||||
JSProxy::cast(result)->set_padding(Smi::FromInt(0));
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -3414,6 +3413,36 @@ MaybeObject* Heap::CopyJSObject(JSObject* source) {
|
||||
}
|
||||
|
||||
|
||||
MaybeObject* Heap::ReinitializeJSProxyAsJSObject(JSProxy* object) {
|
||||
// Allocate fresh map.
|
||||
// TODO(rossberg): Once we optimize proxies, cache these maps.
|
||||
Map* map;
|
||||
MaybeObject* maybe_map_obj =
|
||||
AllocateMap(JS_OBJECT_TYPE, JSObject::kHeaderSize);
|
||||
if (!maybe_map_obj->To<Map>(&map)) return maybe_map_obj;
|
||||
|
||||
// Check that the receiver has the same size as a fresh object.
|
||||
ASSERT(map->instance_size() == object->map()->instance_size());
|
||||
|
||||
map->set_prototype(object->map()->prototype());
|
||||
|
||||
// Allocate the backing storage for the properties.
|
||||
int prop_size = map->unused_property_fields() - map->inobject_properties();
|
||||
Object* properties;
|
||||
{ MaybeObject* maybe_properties = AllocateFixedArray(prop_size, TENURED);
|
||||
if (!maybe_properties->ToObject(&properties)) return maybe_properties;
|
||||
}
|
||||
|
||||
// Reset the map for the object.
|
||||
object->set_map(map);
|
||||
|
||||
// Reinitialize the object from the constructor map.
|
||||
InitializeJSObjectFromMap(JSObject::cast(object),
|
||||
FixedArray::cast(properties), map);
|
||||
return object;
|
||||
}
|
||||
|
||||
|
||||
MaybeObject* Heap::ReinitializeJSGlobalProxy(JSFunction* constructor,
|
||||
JSGlobalProxy* object) {
|
||||
ASSERT(constructor->has_initial_map());
|
||||
|
@ -441,6 +441,11 @@ class Heap {
|
||||
MUST_USE_RESULT MaybeObject* AllocateJSProxy(Object* handler,
|
||||
Object* prototype);
|
||||
|
||||
// Reinitialize a JSProxy into an (empty) JSObject. The receiver
|
||||
// must have the same size as an empty object. The object is reinitialized
|
||||
// and behaves as an object that has been freshly allocated.
|
||||
MUST_USE_RESULT MaybeObject* ReinitializeJSProxyAsJSObject(JSProxy* object);
|
||||
|
||||
// Reinitialize an JSGlobalProxy based on a constructor. The object
|
||||
// must have the same size as objects allocated using the
|
||||
// constructor. The object is reinitialized and behaves as an
|
||||
|
@ -195,7 +195,8 @@ function FormatMessage(message) {
|
||||
non_extensible_proto: ["%0", " is not extensible"],
|
||||
handler_non_object: ["Proxy.", "%0", " called with non-object as handler"],
|
||||
handler_trap_missing: ["Proxy handler ", "%0", " has no '", "%1", "' trap"],
|
||||
handler_failed: ["Proxy handler ", "%0", " returned false for '", "%1", "' trap"],
|
||||
handler_returned_false: ["Proxy handler ", "%0", " returned false for '", "%1", "' trap"],
|
||||
handler_returned_undefined: ["Proxy handler ", "%0", " returned undefined for '", "%1", "' trap"],
|
||||
proxy_prop_not_configurable: ["Trap ", "%1", " of proxy handler ", "%0", " returned non-configurable descriptor for property ", "%2"],
|
||||
proxy_non_object_prop_names: ["Trap ", "%1", " returned non-object ", "%0"],
|
||||
proxy_repeated_prop_name: ["Trap ", "%1", " returned repeated property name ", "%2"],
|
||||
|
@ -3761,6 +3761,7 @@ void JSBuiltinsObject::set_javascript_builtin_code(Builtins::JavaScript id,
|
||||
|
||||
|
||||
ACCESSORS(JSProxy, handler, Object, kHandlerOffset)
|
||||
ACCESSORS(JSProxy, padding, Object, kPaddingOffset)
|
||||
|
||||
|
||||
Address Foreign::address() {
|
||||
|
@ -2301,6 +2301,18 @@ MUST_USE_RESULT PropertyAttributes JSProxy::GetPropertyAttributeWithHandler(
|
||||
}
|
||||
|
||||
|
||||
void JSProxy::Fix() {
|
||||
Isolate* isolate = GetIsolate();
|
||||
HandleScope scope(isolate);
|
||||
Handle<JSProxy> self(this);
|
||||
|
||||
isolate->factory()->BecomeJSObject(self);
|
||||
ASSERT(IsJSObject());
|
||||
// TODO(rossberg): recognize function proxies.
|
||||
}
|
||||
|
||||
|
||||
|
||||
MaybeObject* JSObject::SetPropertyForResult(LookupResult* result,
|
||||
String* name,
|
||||
Object* value,
|
||||
|
@ -6475,6 +6475,9 @@ class JSProxy: public JSReceiver {
|
||||
// [handler]: The handler property.
|
||||
DECL_ACCESSORS(handler, Object)
|
||||
|
||||
// [padding]: The padding slot (unused, see below).
|
||||
DECL_ACCESSORS(padding, Object)
|
||||
|
||||
// Casting.
|
||||
static inline JSProxy* cast(Object* obj);
|
||||
|
||||
@ -6493,6 +6496,9 @@ class JSProxy: public JSReceiver {
|
||||
String* name,
|
||||
bool* has_exception);
|
||||
|
||||
// Turn this into an (empty) JSObject.
|
||||
void Fix();
|
||||
|
||||
// Dispatched behavior.
|
||||
#ifdef OBJECT_PRINT
|
||||
inline void JSProxyPrint() {
|
||||
@ -6504,9 +6510,14 @@ class JSProxy: public JSReceiver {
|
||||
void JSProxyVerify();
|
||||
#endif
|
||||
|
||||
// Layout description.
|
||||
// Layout description. We add padding so that a proxy has the same
|
||||
// size as a virgin JSObject. This is essential for becoming a JSObject
|
||||
// upon freeze.
|
||||
static const int kHandlerOffset = HeapObject::kHeaderSize;
|
||||
static const int kSize = kHandlerOffset + kPointerSize;
|
||||
static const int kPaddingOffset = kHandlerOffset + kPointerSize;
|
||||
static const int kSize = kPaddingOffset + kPointerSize;
|
||||
|
||||
STATIC_CHECK(kSize == JSObject::kHeaderSize);
|
||||
|
||||
typedef FixedBodyDescriptor<kHandlerOffset,
|
||||
kHandlerOffset + kPointerSize,
|
||||
|
@ -615,6 +615,14 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_GetHandler) {
|
||||
}
|
||||
|
||||
|
||||
RUNTIME_FUNCTION(MaybeObject*, Runtime_Fix) {
|
||||
ASSERT(args.length() == 1);
|
||||
CONVERT_CHECKED(JSProxy, proxy, args[0]);
|
||||
proxy->Fix();
|
||||
return proxy;
|
||||
}
|
||||
|
||||
|
||||
RUNTIME_FUNCTION(MaybeObject*, Runtime_ClassOf) {
|
||||
NoHandleAllocation ha;
|
||||
ASSERT(args.length() == 1);
|
||||
|
@ -284,6 +284,7 @@ namespace internal {
|
||||
F(CreateJSProxy, 2, 1) \
|
||||
F(IsJSProxy, 1, 1) \
|
||||
F(GetHandler, 1, 1) \
|
||||
F(Fix, 1, 1) \
|
||||
\
|
||||
/* Statements */ \
|
||||
F(NewClosure, 3, 1) \
|
||||
|
@ -663,7 +663,8 @@ function DefineProxyProperty(obj, p, attributes, should_throw) {
|
||||
var result = %_CallFunction(handler, p, attributes, defineProperty);
|
||||
if (!ToBoolean(result)) {
|
||||
if (should_throw) {
|
||||
throw MakeTypeError("handler_failed", [handler, "defineProperty"]);
|
||||
throw MakeTypeError("handler_returned_false",
|
||||
[handler, "defineProperty"]);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@ -1020,11 +1021,30 @@ function ObjectDefineProperties(obj, properties) {
|
||||
}
|
||||
|
||||
|
||||
// Harmony proxies.
|
||||
function ProxyFix(obj) {
|
||||
var handler = %GetHandler(obj);
|
||||
var fix = handler.fix;
|
||||
if (IS_UNDEFINED(fix)) {
|
||||
throw MakeTypeError("handler_trap_missing", [handler, "fix"]);
|
||||
}
|
||||
var props = %_CallFunction(handler, fix);
|
||||
if (IS_UNDEFINED(props)) {
|
||||
throw MakeTypeError("handler_returned_undefined", [handler, "fix"]);
|
||||
}
|
||||
%Fix(obj);
|
||||
ObjectDefineProperties(obj, props);
|
||||
}
|
||||
|
||||
|
||||
// ES5 section 15.2.3.8.
|
||||
function ObjectSeal(obj) {
|
||||
if (!IS_SPEC_OBJECT(obj)) {
|
||||
throw MakeTypeError("obj_ctor_property_non_object", ["seal"]);
|
||||
}
|
||||
if (%IsJSProxy(obj)) {
|
||||
ProxyFix(obj);
|
||||
}
|
||||
var names = ObjectGetOwnPropertyNames(obj);
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
var name = names[i];
|
||||
@ -1034,7 +1054,8 @@ function ObjectSeal(obj) {
|
||||
DefineOwnProperty(obj, name, desc, true);
|
||||
}
|
||||
}
|
||||
return ObjectPreventExtension(obj);
|
||||
%PreventExtensions(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
@ -1043,6 +1064,9 @@ function ObjectFreeze(obj) {
|
||||
if (!IS_SPEC_OBJECT(obj)) {
|
||||
throw MakeTypeError("obj_ctor_property_non_object", ["freeze"]);
|
||||
}
|
||||
if (%IsJSProxy(obj)) {
|
||||
ProxyFix(obj);
|
||||
}
|
||||
var names = ObjectGetOwnPropertyNames(obj);
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
var name = names[i];
|
||||
@ -1053,7 +1077,8 @@ function ObjectFreeze(obj) {
|
||||
DefineOwnProperty(obj, name, desc, true);
|
||||
}
|
||||
}
|
||||
return ObjectPreventExtension(obj);
|
||||
%PreventExtensions(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
@ -1062,6 +1087,9 @@ function ObjectPreventExtension(obj) {
|
||||
if (!IS_SPEC_OBJECT(obj)) {
|
||||
throw MakeTypeError("obj_ctor_property_non_object", ["preventExtension"]);
|
||||
}
|
||||
if (%IsJSProxy(obj)) {
|
||||
ProxyFix(obj);
|
||||
}
|
||||
%PreventExtensions(obj);
|
||||
return obj;
|
||||
}
|
||||
@ -1072,6 +1100,9 @@ function ObjectIsSealed(obj) {
|
||||
if (!IS_SPEC_OBJECT(obj)) {
|
||||
throw MakeTypeError("obj_ctor_property_non_object", ["isSealed"]);
|
||||
}
|
||||
if (%IsJSProxy(obj)) {
|
||||
return false;
|
||||
}
|
||||
var names = ObjectGetOwnPropertyNames(obj);
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
var name = names[i];
|
||||
@ -1090,6 +1121,9 @@ function ObjectIsFrozen(obj) {
|
||||
if (!IS_SPEC_OBJECT(obj)) {
|
||||
throw MakeTypeError("obj_ctor_property_non_object", ["isFrozen"]);
|
||||
}
|
||||
if (%IsJSProxy(obj)) {
|
||||
return false;
|
||||
}
|
||||
var names = ObjectGetOwnPropertyNames(obj);
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
var name = names[i];
|
||||
@ -1109,6 +1143,9 @@ function ObjectIsExtensible(obj) {
|
||||
if (!IS_SPEC_OBJECT(obj)) {
|
||||
throw MakeTypeError("obj_ctor_property_non_object", ["isExtensible"]);
|
||||
}
|
||||
if (%IsJSProxy(obj)) {
|
||||
return true;
|
||||
}
|
||||
return %IsExtensible(obj);
|
||||
}
|
||||
|
||||
|
@ -529,3 +529,84 @@ TestKeys([], {
|
||||
},
|
||||
getOwnPropertyDescriptor: function(k) { return {} }
|
||||
})
|
||||
|
||||
|
||||
|
||||
// Fixing (Object.freeze, Object.seal, Object.preventExtensions,
|
||||
// Object.isFrozen, Object.isSealed, Object.isExtensible)
|
||||
|
||||
function TestFix(names, handler) {
|
||||
var proto = {p: 77}
|
||||
var assertFixing = function(o, s, f, e) {
|
||||
assertEquals(s, Object.isSealed(o))
|
||||
assertEquals(f, Object.isFrozen(o))
|
||||
assertEquals(e, Object.isExtensible(o))
|
||||
}
|
||||
|
||||
var o1 = Proxy.create(handler, proto)
|
||||
assertFixing(o1, false, false, true)
|
||||
Object.seal(o1)
|
||||
assertFixing(o1, true, names.length === 0, false)
|
||||
assertArrayEquals(names.sort(), Object.getOwnPropertyNames(o1).sort())
|
||||
assertArrayEquals(names.filter(function(x) {return x < "z"}).sort(),
|
||||
Object.keys(o1).sort())
|
||||
assertEquals(proto, Object.getPrototypeOf(o1))
|
||||
assertEquals(77, o1.p)
|
||||
for (var n in o1) {
|
||||
var desc = Object.getOwnPropertyDescriptor(o1, n)
|
||||
if (desc !== undefined) assertFalse(desc.configurable)
|
||||
}
|
||||
|
||||
var o2 = Proxy.create(handler, proto)
|
||||
assertFixing(o2, false, false, true)
|
||||
Object.freeze(o2)
|
||||
assertFixing(o2, true, true, false)
|
||||
assertArrayEquals(names.sort(), Object.getOwnPropertyNames(o2).sort())
|
||||
assertArrayEquals(names.filter(function(x) {return x < "z"}).sort(),
|
||||
Object.keys(o2).sort())
|
||||
assertEquals(proto, Object.getPrototypeOf(o2))
|
||||
assertEquals(77, o2.p)
|
||||
for (var n in o2) {
|
||||
var desc = Object.getOwnPropertyDescriptor(o2, n)
|
||||
if (desc !== undefined) assertFalse(desc.writable)
|
||||
if (desc !== undefined) assertFalse(desc.configurable)
|
||||
}
|
||||
|
||||
var o3 = Proxy.create(handler, proto)
|
||||
assertFixing(o3, false, false, true)
|
||||
Object.preventExtensions(o3)
|
||||
assertFixing(o3, names.length === 0, names.length === 0, false)
|
||||
assertArrayEquals(names.sort(), Object.getOwnPropertyNames(o3).sort())
|
||||
assertArrayEquals(names.filter(function(x) {return x < "z"}).sort(),
|
||||
Object.keys(o3).sort())
|
||||
assertEquals(proto, Object.getPrototypeOf(o3))
|
||||
assertEquals(77, o3.p)
|
||||
}
|
||||
|
||||
TestFix([], {
|
||||
fix: function() { return {} }
|
||||
})
|
||||
TestFix(["a", "b", "c", "d", "zz"], {
|
||||
fix: function() {
|
||||
return {
|
||||
a: {value: "a", writable: true, configurable: false, enumerable: true},
|
||||
b: {value: 33, writable: false, configurable: false, enumerable: true},
|
||||
c: {value: 0, writable: true, configurable: true, enumerable: true},
|
||||
d: {value: true, writable: false, configurable: true, enumerable: true},
|
||||
zz: {value: 0, enumerable: false}
|
||||
}
|
||||
}
|
||||
})
|
||||
TestFix(["a"], {
|
||||
fix: function() { return this.fix2() },
|
||||
fix2: function() {
|
||||
return {a: {value: 4, writable: true, configurable: true, enumerable: true}}
|
||||
}
|
||||
})
|
||||
TestFix(["b"], {
|
||||
get fix() {
|
||||
return function() {
|
||||
return {b: {configurable: true, writable: true, enumerable: true}}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user