diff --git a/js/src/jit-test/tests/proxy/testDirectProxyPreventExtensions1.js b/js/src/jit-test/tests/proxy/testDirectProxyPreventExtensions1.js new file mode 100644 index 000000000000..f668719c6540 --- /dev/null +++ b/js/src/jit-test/tests/proxy/testDirectProxyPreventExtensions1.js @@ -0,0 +1,6 @@ +// Forward to the target if the trap is not defined +target = {}; +var proxy = new Proxy(target, {}); +Object.preventExtensions(proxy); +assertEq(Object.isExtensible(target), false); +assertEq(Object.isExtensible(proxy), false); diff --git a/js/src/jit-test/tests/proxy/testDirectProxyPreventExtensions2.js b/js/src/jit-test/tests/proxy/testDirectProxyPreventExtensions2.js new file mode 100644 index 000000000000..cc6db477b600 --- /dev/null +++ b/js/src/jit-test/tests/proxy/testDirectProxyPreventExtensions2.js @@ -0,0 +1,17 @@ +/* + * Call the trap with the handler as the this value, and the target as the first + * argument + */ +var target = {}; +var called = false; +var handler = { + preventExtensions: function (target1) { + assertEq(this, handler); + assertEq(target1, target); + called = true; + Object.preventExtensions(target); + return true; + } +}; +Object.preventExtensions(new Proxy(target, handler)); +assertEq(called, true); diff --git a/js/src/jit-test/tests/proxy/testDirectProxyPreventExtensions3.js b/js/src/jit-test/tests/proxy/testDirectProxyPreventExtensions3.js new file mode 100644 index 000000000000..e04e1073fad9 --- /dev/null +++ b/js/src/jit-test/tests/proxy/testDirectProxyPreventExtensions3.js @@ -0,0 +1,13 @@ +load(libdir + "asserts.js"); + +/* + * Throw a TypeError if the trap reports that the proxy has been made + * non-extensible, while the target is still extensible + */ +assertThrowsInstanceOf(function () { + Object.preventExtensions(new Proxy({}, { + preventExtensions: function (target) { + return true; + } + })); +}, TypeError); diff --git a/js/src/jit-test/tests/proxy/testDirectProxyPreventExtensions4.js b/js/src/jit-test/tests/proxy/testDirectProxyPreventExtensions4.js new file mode 100644 index 000000000000..4333464ba294 --- /dev/null +++ b/js/src/jit-test/tests/proxy/testDirectProxyPreventExtensions4.js @@ -0,0 +1,13 @@ +load(libdir + "asserts.js"); + +/* + * Throw a TypeError if the trap reports that the proxy cannot be made + * non-extensible + */ +assertThrowsInstanceOf(function () { + Object.preventExtensions(new Proxy({}, { + preventExtensions: function (target) { + return false; + } + })); +}, TypeError); diff --git a/js/src/jit-test/tests/proxy/testDirectProxyPreventExtensions5.js b/js/src/jit-test/tests/proxy/testDirectProxyPreventExtensions5.js new file mode 100644 index 000000000000..d0b618fc123e --- /dev/null +++ b/js/src/jit-test/tests/proxy/testDirectProxyPreventExtensions5.js @@ -0,0 +1,12 @@ +// Reflect side-effects from the trap +target = {}; +var proxy = new Proxy(target, { + preventExtensions: function (target) { + Object.preventExtensions(target); + return true; + } +}); +Object.preventExtensions(proxy); +assertEq(Object.isExtensible(target), false); +assertEq(Object.isExtensible(proxy), false); + diff --git a/js/src/js.msg b/js/src/js.msg index 92d4519c6f96..8128405d81ed 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -375,3 +375,5 @@ MSG_DEF(JSMSG_MUST_REPORT_UNDEFINED, 321, 0, JSEXN_TYPEERR, "proxy must report MSG_DEF(JSMSG_CANT_SET_NW_NC, 322, 0, JSEXN_TYPEERR, "proxy can't successfully set a non-writable, non-configurable property") MSG_DEF(JSMSG_CANT_SET_WO_SETTER, 323, 0, JSEXN_TYPEERR, "proxy can't succesfully set an accessor property without a setter") MSG_DEF(JSMSG_DEBUG_BAD_REFERENT, 324, 2, JSEXN_TYPEERR, "{0} does not refer to {1}") +MSG_DEF(JSMSG_CANT_REPORT_NON_EXT, 325, 0, JSEXN_TYPEERR, "proxy can't report extensible object as non-extensible") +MSG_DEF(JSMSG_CANT_MAKE_NON_EXT, 326, 0, JSEXN_TYPEERR, "can't prevent extensions on object") diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 1b4845ed7168..f78861756e70 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -3482,7 +3482,7 @@ JS_DeepFreezeObject(JSContext *cx, JSObject *objArg) assertSameCompartment(cx, obj); /* Assume that non-extensible objects are already deep-frozen, to avoid divergence. */ - if (!obj->isExtensible()) + if (obj->isExtensible()) return true; if (!JSObject::freeze(cx, obj)) diff --git a/js/src/jsobj.h b/js/src/jsobj.h index bf14f7f2cad6..752b62f3328a 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -548,6 +548,9 @@ struct JSObject : public js::ObjectImpl static inline unsigned getSealedOrFrozenAttributes(unsigned attrs, ImmutabilityType it); public: + friend class js::BaseProxyHandler; + + bool isExtensible() const; bool preventExtensions(JSContext *cx); /* ES5 15.2.3.8: non-extensible, all props non-configurable */ diff --git a/js/src/jsproxy.cpp b/js/src/jsproxy.cpp index 306c8cc6d5c5..ae8f98e86919 100644 --- a/js/src/jsproxy.cpp +++ b/js/src/jsproxy.cpp @@ -412,6 +412,12 @@ IndirectProxyHandler::defineProperty(JSContext *cx, JSObject *proxy, jsid id_, JS_DefinePropertyById(cx, obj, id, v, desc->getter, desc->setter, desc->attrs); } +bool +IndirectProxyHandler::preventExtensions(JSContext *cx, JSObject *proxy) +{ + return GetProxyTargetObject(proxy)->preventExtensions(cx); +} + bool IndirectProxyHandler::getOwnPropertyNames(JSContext *cx, JSObject *proxy, AutoIdVector &props) @@ -442,6 +448,12 @@ IndirectProxyHandler::enumerate(JSContext *cx, JSObject *proxy, return GetPropertyNames(cx, target, 0, &props); } +bool +IndirectProxyHandler::isExtensible(JSObject *proxy) +{ + return GetProxyTargetObject(proxy)->isExtensible(); +} + bool IndirectProxyHandler::call(JSContext *cx, JSObject *proxy, unsigned argc, Value *vp) @@ -1031,6 +1043,7 @@ class ScriptedDirectProxyHandler : public DirectProxyHandler { PropertyDescriptor *desc) MOZ_OVERRIDE; virtual bool defineProperty(JSContext *cx, JSObject *proxy, jsid id, PropertyDescriptor *desc) MOZ_OVERRIDE; + virtual bool preventExtensions(JSContext *cx, JSObject *proxy) MOZ_OVERRIDE; virtual bool getOwnPropertyNames(JSContext *cx, JSObject *proxy, AutoIdVector &props); virtual bool delete_(JSContext *cx, JSObject *proxy, jsid id, bool *bp) MOZ_OVERRIDE; virtual bool enumerate(JSContext *cx, JSObject *proxy, AutoIdVector &props) MOZ_OVERRIDE; @@ -1654,6 +1667,47 @@ ScriptedDirectProxyHandler::defineProperty(JSContext *cx, JSObject *proxy_, jsid return TrapDefineOwnProperty(cx, proxy, id, &v); } +bool +ScriptedDirectProxyHandler::preventExtensions(JSContext *cx, JSObject *proxy_) +{ + RootedObject proxy(cx, proxy_); + + // step a + RootedObject handler(cx, GetDirectProxyHandlerObject(proxy)); + + // step b + RootedObject target(cx, GetProxyTargetObject(proxy)); + + // step c + RootedValue trap(cx); + if (!JSObject::getProperty(cx, handler, handler, cx->names().preventExtensions, &trap)) + return false; + + // step d + if (trap.isUndefined()) + return DirectProxyHandler::preventExtensions(cx, proxy_); + + // step e + Value argv[] = { + ObjectValue(*target) + }; + RootedValue trapResult(cx); + if (!Invoke(cx, ObjectValue(*handler), trap, 1, argv, trapResult.address())) + return false; + + // steps f-h + if (ToBoolean(trapResult)) { + if (target->isExtensible()) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_REPORT_NON_EXT); + return false; + } + return true; + } else { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_MAKE_NON_EXT); + return false; + } +} + bool ScriptedDirectProxyHandler::getOwnPropertyNames(JSContext *cx, JSObject *proxy_, AutoIdVector &props) @@ -2286,6 +2340,14 @@ Proxy::defineProperty(JSContext *cx, JSObject *proxy_, jsid id_, const Value &v) Proxy::defineProperty(cx, proxy, id, &desc); } +bool +Proxy::preventExtensions(JSContext *cx, JSObject *proxy_) +{ + JS_CHECK_RECURSION(cx, return false); + RootedObject proxy(cx, proxy_); + return GetProxyHandler(proxy)->preventExtensions(cx, proxy); +} + bool Proxy::getOwnPropertyNames(JSContext *cx, JSObject *proxy_, AutoIdVector &props) { @@ -2453,6 +2515,12 @@ Proxy::iterate(JSContext *cx, HandleObject proxy_, unsigned flags, MutableHandle return EnumeratedIdVectorToIterator(cx, proxy, flags, props, vp); } +bool +Proxy::isExtensible(JSObject *proxy) +{ + return GetProxyHandler(proxy)->isExtensible(proxy); +} + bool Proxy::call(JSContext *cx, JSObject *proxy_, unsigned argc, Value *vp) { diff --git a/js/src/jsproxy.h b/js/src/jsproxy.h index 93a40005f881..cbd451158166 100644 --- a/js/src/jsproxy.h +++ b/js/src/jsproxy.h @@ -49,6 +49,7 @@ class JS_FRIEND_API(Wrapper); class JS_FRIEND_API(BaseProxyHandler) { void *mFamily; bool mHasPrototype; + protected: // Subclasses may set this in their constructor. void setHasPrototype(bool hasPrototype) { mHasPrototype = hasPrototype; } @@ -91,6 +92,7 @@ class JS_FRIEND_API(BaseProxyHandler) { PropertyDescriptor *desc) = 0; virtual bool defineProperty(JSContext *cx, JSObject *proxy, jsid id, PropertyDescriptor *desc) = 0; + virtual bool preventExtensions(JSContext *cx, JSObject *proxy); virtual bool getOwnPropertyNames(JSContext *cx, JSObject *proxy, AutoIdVector &props) = 0; virtual bool delete_(JSContext *cx, JSObject *proxy, jsid id, bool *bp) = 0; @@ -109,6 +111,7 @@ class JS_FRIEND_API(BaseProxyHandler) { Value *vp); /* Spidermonkey extensions. */ + virtual bool isExtensible(JSObject *proxy); virtual bool call(JSContext *cx, JSObject *proxy, unsigned argc, Value *vp); virtual bool construct(JSContext *cx, JSObject *proxy, unsigned argc, Value *argv, Value *rval); virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl, CallArgs args); @@ -148,6 +151,7 @@ class JS_PUBLIC_API(IndirectProxyHandler) : public BaseProxyHandler { PropertyDescriptor *desc) MOZ_OVERRIDE; virtual bool defineProperty(JSContext *cx, JSObject *proxy, jsid id, PropertyDescriptor *desc) MOZ_OVERRIDE; + virtual bool preventExtensions(JSContext *cx, JSObject *proxy) MOZ_OVERRIDE; virtual bool getOwnPropertyNames(JSContext *cx, JSObject *proxy, AutoIdVector &props) MOZ_OVERRIDE; virtual bool delete_(JSContext *cx, JSObject *proxy, jsid id, @@ -156,6 +160,7 @@ class JS_PUBLIC_API(IndirectProxyHandler) : public BaseProxyHandler { AutoIdVector &props) MOZ_OVERRIDE; /* Spidermonkey extensions. */ + virtual bool isExtensible(JSObject *proxy); virtual bool call(JSContext *cx, JSObject *proxy, unsigned argc, Value *vp) MOZ_OVERRIDE; virtual bool construct(JSContext *cx, JSObject *proxy, unsigned argc, @@ -218,6 +223,7 @@ class Proxy { Value *vp); static bool defineProperty(JSContext *cx, JSObject *proxy, jsid id, PropertyDescriptor *desc); static bool defineProperty(JSContext *cx, JSObject *proxy, jsid id, const Value &v); + static bool preventExtensions(JSContext *cx, JSObject *proxy); static bool getOwnPropertyNames(JSContext *cx, JSObject *proxy, AutoIdVector &props); static bool delete_(JSContext *cx, JSObject *proxy, jsid id, bool *bp); static bool enumerate(JSContext *cx, JSObject *proxy, AutoIdVector &props); @@ -234,6 +240,7 @@ class Proxy { static bool iterate(JSContext *cx, HandleObject proxy, unsigned flags, MutableHandleValue vp); /* Spidermonkey extensions. */ + static bool isExtensible(JSObject *proxy); static bool call(JSContext *cx, JSObject *proxy, unsigned argc, Value *vp); static bool construct(JSContext *cx, JSObject *proxy, unsigned argc, Value *argv, Value *rval); static bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl, CallArgs args); diff --git a/js/src/jsscope.cpp b/js/src/jsscope.cpp index 48e22fff8339..96864ae1daa7 100644 --- a/js/src/jsscope.cpp +++ b/js/src/jsscope.cpp @@ -1028,12 +1028,21 @@ Shape::setObjectParent(JSContext *cx, JSObject *parent, TaggedProto proto, Shape return replaceLastProperty(cx, base, proto, last); } +bool +JSObject::isExtensible() const +{ + if (isProxy()) + return Proxy::isExtensible(const_cast(this)); + return !lastProperty()->hasObjectFlag(js::BaseShape::NOT_EXTENSIBLE); +} + bool JSObject::preventExtensions(JSContext *cx) { JS_ASSERT(isExtensible()); - RootedObject self(cx, this); + if (self->isProxy()) + return Proxy::preventExtensions(cx, self); /* * Force lazy properties to be resolved by iterating over the objects' own @@ -1049,6 +1058,38 @@ JSObject::preventExtensions(JSContext *cx) return self->setFlag(cx, BaseShape::NOT_EXTENSIBLE, GENERATE_SHAPE); } +/* + * Per spec, preventExtensions should be a fundamental trap. We don't want to + * force consumers of BaseProxyHandler (i.e. DOM bindings) to implement yet + * another fundamental trap, however, so we provide a default implementation + * here. We cannot simply forward the operation to the target object, since + * BaseProxyHandler is not guaranteed to have one. Instead, we use the proxy's + * shape tree (as we would for normal objects) to store a flag indicating that + * the proxy is not extensible. + */ +bool +BaseProxyHandler::isExtensible(JSObject *proxy) +{ + return !proxy->lastProperty()->hasObjectFlag(js::BaseShape::NOT_EXTENSIBLE); +} + +bool +BaseProxyHandler::preventExtensions(JSContext *cx, JSObject *proxy_) +{ + JS_ASSERT(isExtensible()); + RootedObject proxy(cx, proxy_); + + /* + * Force lazy properties to be resolved by iterating over the objects' own + * properties. + */ + AutoIdVector props(cx); + if (!js::GetPropertyNames(cx, proxy, JSITER_HIDDEN | JSITER_OWNONLY, &props)) + return false; + JS_ASSERT(!proxy->isDenseArray()); + return proxy_->setFlag(cx, BaseShape::NOT_EXTENSIBLE, JSObject::GENERATE_SHAPE); +} + bool JSObject::setFlag(JSContext *cx, /*BaseShape::Flag*/ uint32_t flag_, GenerateShape generateShape) { diff --git a/js/src/jsscope.h b/js/src/jsscope.h index d869196e55cd..a7ec511634ca 100644 --- a/js/src/jsscope.h +++ b/js/src/jsscope.h @@ -15,6 +15,7 @@ #endif #include "jsobj.h" +#include "jsproxy.h" #include "jspropertytree.h" #include "jstypes.h" diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index bfeb290c7d52..6714a05ef281 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -106,6 +106,7 @@ macro(of, of, "of") \ macro(parseFloat, parseFloat, "parseFloat") \ macro(parseInt, parseInt, "parseInt") \ + macro(preventExtensions, preventExtensions, "preventExtensions") \ macro(propertyIsEnumerable, propertyIsEnumerable, "propertyIsEnumerable") \ macro(proto, proto, "__proto__") \ macro(return, return_, "return") \ diff --git a/js/src/vm/ObjectImpl-inl.h b/js/src/vm/ObjectImpl-inl.h index f2265d2fcd81..b30da6f6d76c 100644 --- a/js/src/vm/ObjectImpl-inl.h +++ b/js/src/vm/ObjectImpl-inl.h @@ -100,12 +100,6 @@ js::ObjectImpl::nativeContainsNoAllocation(Shape &shape) return nativeLookupNoAllocation(shape.propid()) == &shape; } -inline bool -js::ObjectImpl::isExtensible() const -{ - return !lastProperty()->hasObjectFlag(BaseShape::NOT_EXTENSIBLE); -} - inline bool js::ObjectImpl::isDenseArray() const { diff --git a/js/src/vm/ObjectImpl.cpp b/js/src/vm/ObjectImpl.cpp index 5a5e57bbd19d..9eb0251b1d8f 100644 --- a/js/src/vm/ObjectImpl.cpp +++ b/js/src/vm/ObjectImpl.cpp @@ -414,7 +414,7 @@ DenseElementsHeader::defineElement(JSContext *cx, Handle obj, uint3 * If the element doesn't exist, we can only add it if the object is * extensible. */ - if (!obj->isExtensible()) { + if (!obj->asObjectPtr()->isExtensible()) { *succeeded = false; if (!shouldThrow) return true; diff --git a/js/src/vm/ObjectImpl.h b/js/src/vm/ObjectImpl.h index 56ab79a1b19b..b7bc0081a251 100644 --- a/js/src/vm/ObjectImpl.h +++ b/js/src/vm/ObjectImpl.h @@ -1016,8 +1016,6 @@ class ObjectImpl : public gc::Cell return type_->proto; } - inline bool isExtensible() const; - /* * XXX Once the property/element split of bug 586842 is complete, these * methods should move back to JSObject.