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:
rossberg@chromium.org 2011-07-18 13:04:52 +00:00
parent ce75f236f4
commit 5e62e325ac
12 changed files with 204 additions and 8 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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