Bug 1052139 - Implement the ability to prevent modifying an extensible object's [[Prototype]]. r=efaust, r=bholley

--HG--
extra : rebase_source : 58fdb9c3b30d4a46984fae6d546fc7ef94dc3d20
This commit is contained in:
Jeff Walden 2014-09-23 13:03:40 -07:00
parent ead35220b9
commit 083ba18ef1
19 changed files with 408 additions and 12 deletions

View File

@ -7,6 +7,7 @@
#include "builtin/Object.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/UniquePtr.h"
#include "jscntxt.h"
@ -22,7 +23,7 @@ using namespace js::types;
using js::frontend::IsIdentifier;
using mozilla::ArrayLength;
using mozilla::UniquePtr;
bool
js::obj_construct(JSContext *cx, unsigned argc, Value *vp)
@ -609,7 +610,12 @@ obj_setPrototypeOf(JSContext *cx, unsigned argc, Value *vp)
/* Step 7. */
if (!success) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_OBJECT_NOT_EXTENSIBLE, "object");
UniquePtr<char[], JS::FreePolicy> bytes(DecompileValueGenerator(cx, JSDVG_SEARCH_STACK,
args[0], NullPtr()));
if (!bytes)
return false;
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_SETPROTOTYPEOF_FAIL,
bytes.get());
return false;
}

View File

@ -2079,6 +2079,25 @@ ByteSize(JSContext *cx, unsigned argc, Value *vp)
return true;
}
static bool
SetImmutablePrototype(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.get(0).isObject()) {
JS_ReportError(cx, "setImmutablePrototype: object expected");
return false;
}
RootedObject obj(cx, &args[0].toObject());
bool succeeded;
if (!JSObject::setImmutablePrototype(cx, obj, &succeeded))
return false;
args.rval().setBoolean(succeeded);
return true;
}
static const JSFunctionSpecWithHelp TestingFunctions[] = {
JS_FN_HELP("gc", ::GC, 0, 0,
"gc([obj] | 'compartment' [, 'shrinking'])",
@ -2401,6 +2420,14 @@ gc::ZealModeHelpText),
" Return the size in bytes occupied by |value|, or |undefined| if value\n"
" is not allocated in memory.\n"),
JS_FN_HELP("setImmutablePrototype", SetImmutablePrototype, 1, 0,
"setImmutablePrototype(obj)",
" Try to make obj's [[Prototype]] immutable, such that subsequent attempts to\n"
" change it will fail. Return true if obj's [[Prototype]] was successfully made\n"
" immutable (or if it already was immutable), false otherwise. Throws in case\n"
" of internal error, or if the operation doesn't even make sense (for example,\n"
" because the object is a revoked proxy)."),
JS_FS_HELP_END
};

View File

@ -352,13 +352,53 @@ class JSObject : public js::gc::Cell
js::TaggedProto getTaggedProto() const {
return type_->proto();
}
bool hasTenuredProto() const;
bool uninlinedIsProxy() const;
JSObject *getProto() const {
MOZ_ASSERT(!uninlinedIsProxy());
return getTaggedProto().toObjectOrNull();
}
// Normal objects and a subset of proxies have uninteresting [[Prototype]].
// For such objects the [[Prototype]] is just a value returned when needed
// for accesses, or modified in response to requests. These objects store
// the [[Prototype]] directly within |obj->type_|.
//
// Proxies that don't have such a simple [[Prototype]] instead have a
// "lazy" [[Prototype]]. Accessing the [[Prototype]] of such an object
// requires going through the proxy handler {get,set}PrototypeOf and
// setImmutablePrototype methods. This is most commonly useful for proxies
// that are wrappers around other objects. If the [[Prototype]] of the
// underlying object changes, the [[Prototype]] of the wrapper must also
// simultaneously change. We implement this by having the handler methods
// simply delegate to the wrapped object, forwarding its response to the
// caller.
//
// This method returns true if this object has a non-simple [[Prototype]]
// as described above, or false otherwise.
bool hasLazyPrototype() const {
bool lazy = getTaggedProto().isLazy();
MOZ_ASSERT_IF(lazy, uninlinedIsProxy());
return lazy;
}
// True iff this object's [[Prototype]] is immutable. Must not be called
// on proxies with lazy [[Prototype]]!
bool nonLazyPrototypeIsImmutable() const {
MOZ_ASSERT(!hasLazyPrototype());
return lastProperty()->hasObjectFlag(js::BaseShape::IMMUTABLE_PROTOTYPE);
}
// Attempt to make |obj|'s [[Prototype]] immutable, such that subsequently
// trying to change it will not work. If an internal error occurred,
// returns false. Otherwise, |*succeeded| is set to true iff |obj|'s
// [[Prototype]] is now immutable.
static bool
setImmutablePrototype(js::ExclusiveContext *cx, JS::HandleObject obj, bool *succeeded);
static inline bool getProto(JSContext *cx, js::HandleObject obj,
js::MutableHandleObject protop);
// Returns false on error, success of operation in outparam.

View File

@ -181,12 +181,23 @@ JSObject::getProto(JSContext *cx, js::HandleObject obj, js::MutableHandleObject
/* static */ inline bool
JSObject::setProto(JSContext *cx, JS::HandleObject obj, JS::HandleObject proto, bool *succeeded)
{
/* Proxies live in their own little world. */
if (obj->getTaggedProto().isLazy()) {
/*
* If |obj| has a "lazy" [[Prototype]], it is 1) a proxy 2) whose handler's
* {get,set}PrototypeOf and setImmutablePrototype methods mediate access to
* |obj.[[Prototype]]|. The Proxy subsystem is responsible for responding
* to such attempts.
*/
if (obj->hasLazyPrototype()) {
MOZ_ASSERT(obj->is<js::ProxyObject>());
return js::Proxy::setPrototypeOf(cx, obj, proto, succeeded);
}
/* Disallow mutation of immutable [[Prototype]]s. */
if (obj->nonLazyPrototypeIsImmutable()) {
*succeeded = false;
return true;
}
/*
* Disallow mutating the [[Prototype]] on ArrayBuffer objects, which
* due to their complicated delegate-object shenanigans can't easily

View File

@ -268,6 +268,9 @@ class JS_FRIEND_API(BaseProxyHandler)
virtual bool getPrototypeOf(JSContext *cx, HandleObject proxy, MutableHandleObject protop) const;
virtual bool setPrototypeOf(JSContext *cx, HandleObject proxy, HandleObject proto, bool *bp) const;
/* Non-standard but conceptual kin to {g,s}etPrototypeOf, so lives here. */
virtual bool setImmutablePrototype(JSContext *cx, HandleObject proxy, bool *succeeded) const;
/*
* These standard internal methods are implemented, as a convenience, so
* that ProxyHandler subclasses don't have to provide every single method.
@ -371,6 +374,8 @@ class JS_PUBLIC_API(DirectProxyHandler) : public BaseProxyHandler
MutableHandleObject protop) const MOZ_OVERRIDE;
virtual bool setPrototypeOf(JSContext *cx, HandleObject proxy, HandleObject proto,
bool *bp) const MOZ_OVERRIDE;
virtual bool setImmutablePrototype(JSContext *cx, HandleObject proxy,
bool *succeeded) const MOZ_OVERRIDE;
virtual bool has(JSContext *cx, HandleObject proxy, HandleId id,
bool *bp) const MOZ_OVERRIDE;
virtual bool get(JSContext *cx, HandleObject proxy, HandleObject receiver,

View File

@ -128,6 +128,8 @@ class JS_FRIEND_API(CrossCompartmentWrapper) : public Wrapper
MutableHandleObject protop) const MOZ_OVERRIDE;
virtual bool setPrototypeOf(JSContext *cx, HandleObject proxy, HandleObject proto,
bool *bp) const MOZ_OVERRIDE;
virtual bool setImmutablePrototype(JSContext *cx, HandleObject proxy,
bool *succeeded) const MOZ_OVERRIDE;
virtual bool has(JSContext *cx, HandleObject wrapper, HandleId id, bool *bp) const MOZ_OVERRIDE;
virtual bool get(JSContext *cx, HandleObject wrapper, HandleObject receiver,
HandleId id, MutableHandleValue vp) const MOZ_OVERRIDE;
@ -186,6 +188,7 @@ class JS_FRIEND_API(SecurityWrapper) : public Base
virtual bool preventExtensions(JSContext *cx, HandleObject wrapper) const MOZ_OVERRIDE;
virtual bool setPrototypeOf(JSContext *cx, HandleObject proxy, HandleObject proto,
bool *bp) const MOZ_OVERRIDE;
virtual bool setImmutablePrototype(JSContext *cx, HandleObject proxy, bool *succeeded) const MOZ_OVERRIDE;
virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
CallArgs args) const MOZ_OVERRIDE;

View File

@ -320,6 +320,13 @@ BaseProxyHandler::setPrototypeOf(JSContext *cx, HandleObject, HandleObject, bool
return false;
}
bool
BaseProxyHandler::setImmutablePrototype(JSContext *cx, HandleObject proxy, bool *succeeded) const
{
*succeeded = false;
return true;
}
bool
BaseProxyHandler::watch(JSContext *cx, HandleObject proxy, HandleId id, HandleObject callable) const
{

View File

@ -431,6 +431,15 @@ CrossCompartmentWrapper::setPrototypeOf(JSContext *cx, HandleObject wrapper,
NOTHING);
}
bool
CrossCompartmentWrapper::setImmutablePrototype(JSContext *cx, HandleObject wrapper, bool *succeeded) const
{
PIERCE(cx, wrapper,
NOTHING,
Wrapper::setImmutablePrototype(cx, wrapper, succeeded),
NOTHING);
}
const CrossCompartmentWrapper CrossCompartmentWrapper::singleton(0u);
bool

View File

@ -126,6 +126,13 @@ DirectProxyHandler::setPrototypeOf(JSContext *cx, HandleObject proxy, HandleObje
return JSObject::setProto(cx, target, proto, bp);
}
bool
DirectProxyHandler::setImmutablePrototype(JSContext *cx, HandleObject proxy, bool *succeeded) const
{
RootedObject target(cx, proxy->as<ProxyObject>().target());
return JSObject::setImmutablePrototype(cx, target, succeeded);
}
bool
DirectProxyHandler::objectClassIs(HandleObject proxy, ESClassValue classValue,
JSContext *cx) const

View File

@ -517,22 +517,30 @@ Proxy::defaultValue(JSContext *cx, HandleObject proxy, JSType hint, MutableHandl
JSObject * const TaggedProto::LazyProto = reinterpret_cast<JSObject *>(0x1);
bool
/* static */ bool
Proxy::getPrototypeOf(JSContext *cx, HandleObject proxy, MutableHandleObject proto)
{
MOZ_ASSERT(proxy->getTaggedProto().isLazy());
MOZ_ASSERT(proxy->hasLazyPrototype());
JS_CHECK_RECURSION(cx, return false);
return proxy->as<ProxyObject>().handler()->getPrototypeOf(cx, proxy, proto);
}
bool
/* static */ bool
Proxy::setPrototypeOf(JSContext *cx, HandleObject proxy, HandleObject proto, bool *bp)
{
MOZ_ASSERT(proxy->getTaggedProto().isLazy());
MOZ_ASSERT(proxy->hasLazyPrototype());
JS_CHECK_RECURSION(cx, return false);
return proxy->as<ProxyObject>().handler()->setPrototypeOf(cx, proxy, proto, bp);
}
/* static */ bool
Proxy::setImmutablePrototype(JSContext *cx, HandleObject proxy, bool *succeeded)
{
JS_CHECK_RECURSION(cx, return false);
const BaseProxyHandler *handler = proxy->as<ProxyObject>().handler();
return handler->setImmutablePrototype(cx, proxy, succeeded);
}
/* static */ bool
Proxy::watch(JSContext *cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleObject callable)
{
@ -859,7 +867,7 @@ ProxyObject::renew(JSContext *cx, const BaseProxyHandler *handler, Value priv)
MOZ_ASSERT(getParent() == cx->global());
MOZ_ASSERT(getClass() == &ProxyObject::class_);
MOZ_ASSERT(!getClass()->ext.innerObject);
MOZ_ASSERT(getTaggedProto().isLazy());
MOZ_ASSERT(hasLazyPrototype());
setHandler(handler);
setCrossCompartmentPrivate(priv);

View File

@ -39,6 +39,7 @@ class Proxy
static bool preventExtensions(JSContext *cx, HandleObject proxy);
static bool getPrototypeOf(JSContext *cx, HandleObject proxy, MutableHandleObject protop);
static bool setPrototypeOf(JSContext *cx, HandleObject proxy, HandleObject proto, bool *bp);
static bool setImmutablePrototype(JSContext *cx, HandleObject proxy, bool *succeeded);
static bool has(JSContext *cx, HandleObject proxy, HandleId id, bool *bp);
static bool get(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id,
MutableHandleValue vp);

View File

@ -357,6 +357,21 @@ ScriptedDirectProxyHandler::setPrototypeOf(JSContext *cx, HandleObject proxy,
return DirectProxyHandler::setPrototypeOf(cx, proxy, proto, bp);
}
// Not yet part of ES6, but hopefully to be standards-tracked -- and needed to
// handle revoked proxies in any event.
bool
ScriptedDirectProxyHandler::setImmutablePrototype(JSContext *cx, HandleObject proxy,
bool *succeeded) const
{
RootedObject target(cx, proxy->as<ProxyObject>().target());
if (!target) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_PROXY_REVOKED);
return false;
}
return DirectProxyHandler::setImmutablePrototype(cx, proxy, succeeded);
}
// Corresponds to the "standard" property descriptor getOwn getPrototypeOf dance. It's so explicit
// here because ScriptedDirectProxyHandler allows script visibility for this operation.
bool

View File

@ -35,6 +35,9 @@ class ScriptedDirectProxyHandler : public DirectProxyHandler {
MutableHandleObject protop) const MOZ_OVERRIDE;
virtual bool setPrototypeOf(JSContext *cx, HandleObject proxy, HandleObject proto,
bool *bp) const MOZ_OVERRIDE;
/* Non-standard, but needed to handle revoked proxies. */
virtual bool setImmutablePrototype(JSContext *cx, HandleObject proxy,
bool *succeeded) const MOZ_OVERRIDE;
virtual bool has(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) const MOZ_OVERRIDE;
virtual bool get(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id,

View File

@ -50,6 +50,15 @@ SecurityWrapper<Base>::nativeCall(JSContext *cx, IsAcceptableThis test, NativeIm
return false;
}
template <class Base>
bool
SecurityWrapper<Base>::setImmutablePrototype(JSContext *cx, HandleObject wrapper,
bool *succeeded) const
{
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNWRAP_DENIED);
return false;
}
template <class Base>
bool
SecurityWrapper<Base>::setPrototypeOf(JSContext *cx, HandleObject wrapper,

View File

@ -0,0 +1,215 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
var gTestfile = "setImmutablePrototype.js";
//-----------------------------------------------------------------------------
var BUGNUMBER = 1052139;
var summary =
"Implement JSAPI and a shell function to prevent modifying an extensible " +
"object's [[Prototype]]";
print(BUGNUMBER + ": " + summary);
/**************
* BEGIN TEST *
**************/
if (typeof evaluate !== "function")
{
// Not totally faithful semantics, but approximately close enough for this
// test's purposes.
evaluate = eval;
}
var usingRealSetImmutablePrototype = true;
if (typeof setImmutablePrototype !== "function")
{
usingRealSetImmutablePrototype = false;
if (typeof SpecialPowers !== "undefined")
{
setImmutablePrototype =
SpecialPowers.Cu.getJSTestingFunctions().setImmutablePrototype;
}
}
if (typeof wrap !== "function")
{
// good enough
wrap = function(x) { return x; };
}
function setViaProtoSetter(obj, newProto)
{
var setter =
Object.getOwnPropertyDescriptor(Object.prototype, "__proto__").set;
setter.call(obj, newProto);
}
function checkPrototypeMutationFailure(obj, desc)
{
var initialProto = Object.getPrototypeOf(obj);
// disconnected from any [[Prototype]] chains for use on any object at all
var newProto = Object.create(null);
function tryMutate(func, method)
{
try
{
func(obj, newProto);
throw new Error(desc + ": no error thrown, prototype " +
(Object.getPrototypeOf(obj) === initialProto
? "wasn't"
: "was") +
" changed");
}
catch (e)
{
// Note: This is always a TypeError from *this* global object, because
// Object.setPrototypeOf and the __proto__ setter come from *this*
// global object.
assertEq(e instanceof TypeError, true,
desc + ": should have thrown TypeError setting [[Prototype]] " +
"via " + method + ", got " + e);
assertEq(Object.getPrototypeOf(obj), initialProto,
desc + ": shouldn't observe [[Prototype]] change");
}
}
tryMutate(Object.setPrototypeOf, "Object.setPrototypeOf");
tryMutate(setViaProtoSetter, "__proto__ setter");
}
function runNormalTests(global)
{
if (typeof setImmutablePrototype !== "function")
{
print("no usable setImmutablePrototype function available, skipping tests");
return;
}
// regular old object, non-null [[Prototype]]
var emptyLiteral = global.evaluate("({})");
assertEq(setImmutablePrototype(emptyLiteral), true);
checkPrototypeMutationFailure(emptyLiteral, "empty literal");
// regular old object, null [[Prototype]]
var nullProto = global.Object.create(null);
assertEq(setImmutablePrototype(nullProto), true);
checkPrototypeMutationFailure(nullProto, "nullProto");
// Shocker: SpecialPowers's little mind doesn't understand proxies. Abort.
if (!usingRealSetImmutablePrototype)
return;
// direct proxies
var emptyTarget = global.evaluate("({})");
var directProxy = new global.Proxy(emptyTarget, {});
assertEq(setImmutablePrototype(directProxy), true);
checkPrototypeMutationFailure(directProxy, "direct proxy to empty target");
checkPrototypeMutationFailure(emptyTarget, "empty target");
var anotherTarget = global.evaluate("({})");
var anotherDirectProxy = new global.Proxy(anotherTarget, {});
assertEq(setImmutablePrototype(anotherTarget), true);
checkPrototypeMutationFailure(anotherDirectProxy, "another direct proxy to empty target");
checkPrototypeMutationFailure(anotherTarget, "another empty target");
var nestedTarget = global.evaluate("({})");
var nestedProxy = new global.Proxy(new Proxy(nestedTarget, {}), {});
assertEq(setImmutablePrototype(nestedProxy), true);
checkPrototypeMutationFailure(nestedProxy, "nested proxy to empty target");
checkPrototypeMutationFailure(nestedTarget, "nested target");
// revocable proxies
var revocableTarget = global.evaluate("({})");
var revocable = global.Proxy.revocable(revocableTarget, {});
assertEq(setImmutablePrototype(revocable.proxy), true);
checkPrototypeMutationFailure(revocableTarget, "revocable target");
checkPrototypeMutationFailure(revocable.proxy, "revocable proxy");
assertEq(revocable.revoke(), undefined);
try
{
setImmutablePrototype(revocable.proxy);
throw new Error("expected to throw on revoked proxy");
}
catch (e)
{
// Note: This is a TypeError from |global|, because the proxy's
// |setImmutablePrototype| method is what actually throws here.
// (Usually the method simply sets |*succeeded| to false and the
// caller handles or throws as needed after that. But not here.)
assertEq(e instanceof global.TypeError, true,
"expected TypeError, instead threw " + e);
}
var anotherRevocableTarget = global.evaluate("({})");
assertEq(setImmutablePrototype(anotherRevocableTarget), true);
checkPrototypeMutationFailure(anotherRevocableTarget, "another revocable target");
var anotherRevocable = global.Proxy.revocable(anotherRevocableTarget, {});
checkPrototypeMutationFailure(anotherRevocable.proxy, "another revocable target");
assertEq(anotherRevocable.revoke(), undefined);
try
{
var rv = setImmutablePrototype(anotherRevocable.proxy);
throw new Error("expected to throw on another revoked proxy, returned " + rv);
}
catch (e)
{
// NOTE: Again from |global|, as above.
assertEq(e instanceof global.TypeError, true,
"expected TypeError, instead threw " + e);
}
// hated indirect proxies
var oldProto = {};
var indirectProxy = global.Proxy.create({}, oldProto);
assertEq(setImmutablePrototype(indirectProxy), true);
assertEq(Object.getPrototypeOf(indirectProxy), oldProto);
checkPrototypeMutationFailure(indirectProxy, "indirectProxy");
var indirectFunctionProxy = global.Proxy.createFunction({}, function call() {});
assertEq(setImmutablePrototype(indirectFunctionProxy), true);
assertEq(Object.getPrototypeOf(indirectFunctionProxy), global.Function.prototype);
checkPrototypeMutationFailure(indirectFunctionProxy, "indirectFunctionProxy");
// more-hated wrap()
var wrappedTarget = {};
var wrappedProxy = global.wrap(wrappedTarget);
assertEq(setImmutablePrototype(wrappedProxy), true);
checkPrototypeMutationFailure(wrappedProxy, "wrapped proxy");
}
var global = this;
runNormalTests(global); // same-global
if (typeof newGlobal === "function")
{
var otherGlobal = newGlobal();
var subsumingNothing = newGlobal({ principal: 0 });
var subsumingEverything = newGlobal({ principal: ~0 });
runNormalTests(otherGlobal); // cross-global
runNormalTests(subsumingNothing);
runNormalTests(subsumingEverything);
}
/******************************************************************************/
if (typeof reportCompare === "function")
reportCompare(true, true);
print("Tests complete");

View File

@ -139,6 +139,21 @@ ObjectElements::MakeElementsCopyOnWrite(ExclusiveContext *cx, NativeObject *obj)
return true;
}
/* static */ bool
JSObject::setImmutablePrototype(ExclusiveContext *cx, HandleObject obj, bool *succeeded)
{
if (obj->hasLazyPrototype()) {
if (!cx->shouldBeJSContext())
return false;
return Proxy::setImmutablePrototype(cx->asJSContext(), obj, succeeded);
}
if (!obj->setFlag(cx, BaseShape::IMMUTABLE_PROTOTYPE))
return false;
*succeeded = true;
return true;
}
#ifdef DEBUG
void
js::NativeObject::checkShapeConsistency()

View File

@ -335,6 +335,7 @@ class BaseShape : public gc::TenuredCell
ITERATED_SINGLETON = 0x200,
NEW_TYPE_UNKNOWN = 0x400,
UNCACHEABLE_PROTO = 0x800,
IMMUTABLE_PROTOTYPE = 0x1000,
// These two flags control which scope a new variables ends up on in the
// scope chain. If the variable is "qualified" (i.e., if it was defined
@ -344,10 +345,10 @@ class BaseShape : public gc::TenuredCell
// incidentally is an error in strict mode) then it goes on the lowest
// scope in the chain with the UNQUALIFIED_VAROBJ flag set (which is
// typically the global).
QUALIFIED_VAROBJ = 0x1000,
UNQUALIFIED_VAROBJ = 0x2000,
QUALIFIED_VAROBJ = 0x2000,
UNQUALIFIED_VAROBJ = 0x4000,
OBJECT_FLAG_MASK = 0x3ff8
OBJECT_FLAG_MASK = 0x7ff8
};
private:

View File

@ -2215,6 +2215,18 @@ XrayWrapper<Base, Traits>::setPrototypeOf(JSContext *cx, JS::HandleObject wrappe
return true;
}
template <typename Base, typename Traits>
bool
XrayWrapper<Base, Traits>::setImmutablePrototype(JSContext *cx, JS::HandleObject wrapper,
bool *succeeded) const
{
// For now, lacking an obvious place to store a bit, prohibit making an
// Xray's [[Prototype]] immutable. We can revisit this (or maybe give all
// Xrays immutable [[Prototype]], because who does this, really?) later if
// necessary.
*succeeded = false;
return true;
}
/*
* The Permissive / Security variants should be used depending on whether the

View File

@ -420,6 +420,8 @@ class XrayWrapper : public Base {
JS::MutableHandleObject protop) const MOZ_OVERRIDE;
virtual bool setPrototypeOf(JSContext *cx, JS::HandleObject wrapper,
JS::HandleObject proto, bool *bp) const MOZ_OVERRIDE;
virtual bool setImmutablePrototype(JSContext *cx, JS::HandleObject wrapper,
bool *succeeded) const MOZ_OVERRIDE;
virtual bool has(JSContext *cx, JS::Handle<JSObject*> wrapper, JS::Handle<jsid> id,
bool *bp) const MOZ_OVERRIDE;
virtual bool get(JSContext *cx, JS::Handle<JSObject*> wrapper, JS::Handle<JSObject*> receiver,