Bug 789897 - Implement a preventExtensions trap for proxies. Patch is a semi-tag-team effort with jwalden. r=jwalden, r=ejpbruel, r=bholley, r=two-turtle-doves-and-a-partridge-in-a-pear-tree
authorEddy Bruel <ejpbruel@mozilla.com>
Fri, 22 Mar 2013 19:43:12 -0700
changeset 126571 804c5ca59d99e89c7ee1e1d223a6ceafd34e0b65
parent 126570 a4e200fe684d5717113a5374ffe5ec3ace820118
child 126572 2e564ec4c11d2cbc37a43e1247c4c72078ebcb24
push id24488
push userryanvm@gmail.com
push dateFri, 29 Mar 2013 00:54:52 +0000
treeherdermozilla-central@8aeabe064932 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwalden, ejpbruel, bholley, two-turtle-doves-and-a-partridge-in-a-pear-tree
bugs789897
milestone22.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 789897 - Implement a preventExtensions trap for proxies. Patch is a semi-tag-team effort with jwalden. r=jwalden, r=ejpbruel, r=bholley, r=two-turtle-doves-and-a-partridge-in-a-pear-tree
js/src/jsproxy.cpp
js/src/jsproxy.h
js/src/jswrapper.cpp
js/src/jswrapper.h
js/src/vm/ObjectImpl-inl.h
js/src/vm/ObjectImpl.h
js/src/vm/Shape.cpp
js/xpconnect/wrappers/XrayWrapper.cpp
js/xpconnect/wrappers/XrayWrapper.h
--- a/js/src/jsproxy.cpp
+++ b/js/src/jsproxy.cpp
@@ -311,16 +311,24 @@ BaseProxyHandler::isExtensible(JSObject 
 {
     // Unaltered proxies always claim to be extensible.  (Whether defining a
     // property will actually *work* is an entirely different question.)  As a
     // necessary consequence, they can't be made non-extensible.
     return true;
 }
 
 bool
+BaseProxyHandler::preventExtensions(JSContext *cx, HandleObject proxy)
+{
+    // See above.
+    JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_CHANGE_EXTENSIBILITY);
+    return false;
+}
+
+bool
 BaseProxyHandler::call(JSContext *cx, HandleObject proxy, unsigned argc,
                        Value *vp)
 {
     assertEnteredPolicy(cx, proxy, JSID_VOID);
     AutoValueRooter rval(cx);
     RootedValue call(cx, GetCall(proxy));
     JSBool ok = Invoke(cx, vp[1], call, argc, JS_ARGV(cx, vp), rval.addr());
     if (ok)
@@ -646,16 +654,23 @@ DirectProxyHandler::iterate(JSContext *c
 }
 
 bool
 DirectProxyHandler::isExtensible(JSObject *proxy)
 {
     return GetProxyTargetObject(proxy)->isExtensible();
 }
 
+bool
+DirectProxyHandler::preventExtensions(JSContext *cx, HandleObject proxy)
+{
+    RootedObject target(cx, GetProxyTargetObject(proxy));
+    return JSObject::preventExtensions(cx, target);
+}
+
 static bool
 GetFundamentalTrap(JSContext *cx, HandleObject handler, HandlePropertyName name,
                    MutableHandleValue fvalp)
 {
     JS_CHECK_RECURSION(cx, return false);
 
     return JSObject::getProperty(cx, handler, handler, name, fvalp);
 }
@@ -2485,16 +2500,24 @@ Proxy::iterate(JSContext *cx, HandleObje
         ? !Proxy::keys(cx, proxy, props)
         : !Proxy::enumerate(cx, proxy, props)) {
         return false;
     }
     return EnumeratedIdVectorToIterator(cx, proxy, flags, props, vp);
 }
 
 bool
+Proxy::preventExtensions(JSContext *cx, HandleObject proxy)
+{
+    JS_CHECK_RECURSION(cx, return false);
+    BaseProxyHandler *handler = GetProxyHandler(proxy);
+    return handler->preventExtensions(cx, proxy);
+}
+
+bool
 Proxy::isExtensible(JSObject *proxy)
 {
     return GetProxyHandler(proxy)->isExtensible(proxy);
 }
 
 bool
 Proxy::call(JSContext *cx, HandleObject proxy, unsigned argc, Value *vp)
 {
--- a/js/src/jsproxy.h
+++ b/js/src/jsproxy.h
@@ -99,16 +99,17 @@ class JS_FRIEND_API(BaseProxyHandler) {
         GET,
         SET,
         CALL
     };
     virtual bool enter(JSContext *cx, HandleObject wrapper, HandleId id, Action act,
                        bool *bp);
 
     /* ES5 Harmony fundamental proxy traps. */
+    virtual bool preventExtensions(JSContext *cx, HandleObject proxy);
     virtual bool getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
                                        PropertyDescriptor *desc, unsigned flags) = 0;
     virtual bool getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy,
                                           HandleId id, PropertyDescriptor *desc,
                                           unsigned flags) = 0;
     virtual bool defineProperty(JSContext *cx, HandleObject proxy, HandleId id,
                                 PropertyDescriptor *desc) = 0;
     virtual bool getOwnPropertyNames(JSContext *cx, HandleObject proxy,
@@ -154,16 +155,17 @@ class JS_FRIEND_API(BaseProxyHandler) {
  * allows consumers of this class to forward to another object as transparently
  * and efficiently as possible.
  */
 class JS_PUBLIC_API(DirectProxyHandler) : public BaseProxyHandler {
 public:
     explicit DirectProxyHandler(void *family);
 
     /* ES5 Harmony fundamental proxy traps. */
+    virtual bool preventExtensions(JSContext *cx, HandleObject proxy) MOZ_OVERRIDE;
     virtual bool getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
                                        PropertyDescriptor *desc, unsigned flags) MOZ_OVERRIDE;
     virtual bool getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy,
                                           HandleId id, PropertyDescriptor *desc,
                                           unsigned flags) MOZ_OVERRIDE;
     virtual bool defineProperty(JSContext *cx, HandleObject proxy, HandleId id,
                                 PropertyDescriptor *desc) MOZ_OVERRIDE;
     virtual bool getOwnPropertyNames(JSContext *cx, HandleObject proxy,
@@ -204,16 +206,17 @@ public:
                               MutableHandleValue vp) MOZ_OVERRIDE;
     virtual JSObject *weakmapKeyDelegate(JSObject *proxy);
 };
 
 /* Dispatch point for handlers that executes the appropriate C++ or scripted traps. */
 class Proxy {
   public:
     /* ES5 Harmony fundamental proxy traps. */
+    static bool preventExtensions(JSContext *cx, HandleObject proxy);
     static bool getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
                                       PropertyDescriptor *desc, unsigned flags);
     static bool getPropertyDescriptor(JSContext *cx, HandleObject proxy, unsigned flags, HandleId id,
                                       MutableHandleValue vp);
     static bool getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
                                          PropertyDescriptor *desc, unsigned flags);
     static bool getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, unsigned flags, HandleId id,
                                          MutableHandleValue vp);
--- a/js/src/jswrapper.cpp
+++ b/js/src/jswrapper.cpp
@@ -633,26 +633,35 @@ SecurityWrapper<Base>::isExtensible(JSOb
     // Just like BaseProxyHandler, SecurityWrappers claim by default to always
     // be extensible, so as not to leak information about the state of the
     // underlying wrapped thing.
     return true;
 }
 
 template <class Base>
 bool
+SecurityWrapper<Base>::preventExtensions(JSContext *cx, HandleObject wrapper)
+{
+    // See above.
+    JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_UNWRAP_DENIED);
+    return false;
+}
+
+template <class Base>
+bool
 SecurityWrapper<Base>::enter(JSContext *cx, HandleObject wrapper, HandleId id,
                              Wrapper::Action act, bool *bp)
 {
     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_UNWRAP_DENIED);
     *bp = false;
     return false;
 }
 
- template <class Base>
- bool
+template <class Base>
+bool
 SecurityWrapper<Base>::nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
                                   CallArgs args)
 {
     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_UNWRAP_DENIED);
     return false;
 }
 
 template <class Base>
--- a/js/src/jswrapper.h
+++ b/js/src/jswrapper.h
@@ -139,16 +139,17 @@ class JS_FRIEND_API(CrossCompartmentWrap
  */
 template <class Base>
 class JS_FRIEND_API(SecurityWrapper) : public Base
 {
   public:
     SecurityWrapper(unsigned flags);
 
     virtual bool isExtensible(JSObject *wrapper) MOZ_OVERRIDE;
+    virtual bool preventExtensions(JSContext *cx, HandleObject wrapper) MOZ_OVERRIDE;
     virtual bool enter(JSContext *cx, HandleObject wrapper, HandleId id, Wrapper::Action act,
                        bool *bp) MOZ_OVERRIDE;
     virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
                             CallArgs args) MOZ_OVERRIDE;
     virtual bool objectClassIs(HandleObject obj, ESClassValue classValue,
                                JSContext *cx) MOZ_OVERRIDE;
     virtual bool regexp_toShared(JSContext *cx, HandleObject proxy, RegExpGuard *g) MOZ_OVERRIDE;
 
--- a/js/src/vm/ObjectImpl-inl.h
+++ b/js/src/vm/ObjectImpl-inl.h
@@ -88,17 +88,16 @@ js::ObjectImpl::isExtensible() const
 {
     if (this->isProxy())
         return Proxy::isExtensible(const_cast<JSObject*>(this->asObjectPtr()));
 
     // [[Extensible]] for ordinary non-proxy objects is an object flag.
     return !lastProperty()->hasObjectFlag(BaseShape::NOT_EXTENSIBLE);
 }
 
-
 inline uint32_t
 js::ObjectImpl::getDenseInitializedLength()
 {
     MOZ_ASSERT(isNative());
     return getElementsHeader()->initializedLength;
 }
 
 inline js::HeapSlotArray
--- a/js/src/vm/ObjectImpl.h
+++ b/js/src/vm/ObjectImpl.h
@@ -1107,16 +1107,18 @@ class ObjectImpl : public gc::Cell
     }
 
     Class *getClass() const {
         return type_->clasp;
     }
 
     inline bool isExtensible() const;
 
+    // Attempt to change the [[Extensible]] bit on |obj| to false.  Callers
+    // must ensure that |obj| is currently extensible before calling this!
     static bool
     preventExtensions(JSContext *cx, Handle<ObjectImpl*> obj);
 
     inline HeapSlotArray getDenseElements();
     inline const Value & getDenseElement(uint32_t idx);
     inline bool containsDenseElement(uint32_t idx);
     inline uint32_t getDenseInitializedLength();
 
--- a/js/src/vm/Shape.cpp
+++ b/js/src/vm/Shape.cpp
@@ -1026,17 +1026,24 @@ Shape::setObjectParent(JSContext *cx, JS
 
     RootedShape lastRoot(cx, last);
     return replaceLastProperty(cx, base, proto, lastRoot);
 }
 
 /* static */ bool
 js::ObjectImpl::preventExtensions(JSContext *cx, Handle<ObjectImpl*> obj)
 {
-    JS_ASSERT(obj->isExtensible());
+    MOZ_ASSERT(obj->isExtensible(),
+               "Callers must ensure |obj| is extensible before calling "
+               "preventExtensions");
+
+    if (obj->isProxy()) {
+        RootedObject object(cx, obj->asObjectPtr());
+        return js::Proxy::preventExtensions(cx, object);
+    }
 
     RootedObject self(cx, obj->asObjectPtr());
 
     /*
      * Force lazy properties to be resolved by iterating over the objects' own
      * properties.
      */
     AutoIdVector props(cx);
--- a/js/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/xpconnect/wrappers/XrayWrapper.cpp
@@ -1425,16 +1425,25 @@ XrayWrapper<Base, Traits>::isExtensible(
     // even if that script freezes the reflector, we don't want to make that
     // visible to the caller. DOM reflectors are always extensible by default,
     // so we can just return true here.
     return true;
 }
 
 template <typename Base, typename Traits>
 bool
+XrayWrapper<Base, Traits>::preventExtensions(JSContext *cx, JS::Handle<JSObject*> wrapper)
+{
+    // See above.
+    JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_CHANGE_EXTENSIBILITY);
+    return false;
+}
+
+template <typename Base, typename Traits>
+bool
 XrayWrapper<Base, Traits>::getPropertyDescriptor(JSContext *cx, JS::Handle<JSObject*> wrapper,
                                                  JS::Handle<jsid> id,
                                                  js::PropertyDescriptor *desc, unsigned flags)
 {
     assertEnteredPolicy(cx, wrapper, id);
     JSObject *holder = Traits::singleton.ensureHolder(cx, wrapper);
     if (Traits::isResolving(cx, holder, id)) {
         desc->obj = NULL;
--- a/js/xpconnect/wrappers/XrayWrapper.h
+++ b/js/xpconnect/wrappers/XrayWrapper.h
@@ -62,16 +62,17 @@ XrayTraits* GetXrayTraits(JSObject *obj)
 template <typename Base, typename Traits = XPCWrappedNativeXrayTraits >
 class XrayWrapper : public Base {
   public:
     XrayWrapper(unsigned flags);
     virtual ~XrayWrapper();
 
     /* Fundamental proxy traps. */
     virtual bool isExtensible(JSObject *wrapper) MOZ_OVERRIDE;
+    virtual bool preventExtensions(JSContext *cx, JS::Handle<JSObject*> wrapper) MOZ_OVERRIDE;
     virtual bool getPropertyDescriptor(JSContext *cx, JS::Handle<JSObject*> wrapper, JS::Handle<jsid> id,
                                        js::PropertyDescriptor *desc, unsigned flags);
     virtual bool getOwnPropertyDescriptor(JSContext *cx, JS::Handle<JSObject*> wrapper, JS::Handle<jsid> id,
                                           js::PropertyDescriptor *desc,
                                           unsigned flags);
     virtual bool defineProperty(JSContext *cx, JS::Handle<JSObject*> wrapper, JS::Handle<jsid> id,
                                 js::PropertyDescriptor *desc);
     virtual bool getOwnPropertyNames(JSContext *cx, JS::Handle<JSObject*> wrapper,