зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1052139 - Implement the ability to prevent modifying an extensible object's [[Prototype]]. r=efaust, r=bholley
--HG-- extra : rebase_source : 547490455fc588e40dd2b2dba4c3355768d78f8d
This commit is contained in:
Родитель
5573b02492
Коммит
02d8b3daa4
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -2078,6 +2078,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'])",
|
||||
|
@ -2400,6 +2419,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
|
||||
};
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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");
|
|
@ -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()
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
Загрузка…
Ссылка в новой задаче