зеркало из https://github.com/mozilla/gecko-dev.git
Bug 770344 - Experiment implementing __proto__ as an accessor. r=luke
This commit is contained in:
Родитель
2e645f31ef
Коммит
68e5a1ecb2
|
@ -4042,7 +4042,7 @@ struct JSClass {
|
|||
* with the following flags. Failure to use JSCLASS_GLOBAL_FLAGS was
|
||||
* prevously allowed, but is now an ES5 violation and thus unsupported.
|
||||
*/
|
||||
#define JSCLASS_GLOBAL_SLOT_COUNT (JSProto_LIMIT * 3 + 19)
|
||||
#define JSCLASS_GLOBAL_SLOT_COUNT (JSProto_LIMIT * 3 + 20)
|
||||
#define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n) \
|
||||
(JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n)))
|
||||
#define JSCLASS_GLOBAL_FLAGS \
|
||||
|
|
|
@ -452,7 +452,9 @@ JS_GetE4XObjectsCreated(JSContext *)
|
|||
return sE4XObjectsCreated;
|
||||
}
|
||||
|
||||
namespace js {
|
||||
extern size_t sSetProtoCalled;
|
||||
}
|
||||
|
||||
JS_FRIEND_API(size_t)
|
||||
JS_SetProtoCalled(JSContext *)
|
||||
|
|
|
@ -103,60 +103,6 @@ JS_ObjectToOuterObject(JSContext *cx, JSObject *obj_)
|
|||
return GetOuterObject(cx, obj);
|
||||
}
|
||||
|
||||
#if JS_HAS_OBJ_PROTO_PROP
|
||||
|
||||
static JSBool
|
||||
obj_getProto(JSContext *cx, HandleObject obj, HandleId id, Value *vp);
|
||||
|
||||
static JSBool
|
||||
obj_setProto(JSContext *cx, HandleObject obj, HandleId id, JSBool strict, Value *vp);
|
||||
|
||||
JSPropertySpec object_props[] = {
|
||||
{js_proto_str, 0, JSPROP_PERMANENT|JSPROP_SHARED, obj_getProto, obj_setProto},
|
||||
{0,0,0,0,0}
|
||||
};
|
||||
|
||||
static JSBool
|
||||
obj_getProto(JSContext *cx, HandleObject obj, HandleId id, Value *vp)
|
||||
{
|
||||
/* Let CheckAccess get the slot's value, based on the access mode. */
|
||||
unsigned attrs;
|
||||
RootedId nid(cx, NameToId(cx->runtime->atomState.protoAtom));
|
||||
return CheckAccess(cx, obj, nid, JSACC_PROTO, vp, &attrs);
|
||||
}
|
||||
|
||||
size_t sSetProtoCalled = 0;
|
||||
|
||||
static JSBool
|
||||
obj_setProto(JSContext *cx, HandleObject obj, HandleId id, JSBool strict, Value *vp)
|
||||
{
|
||||
if (!cx->runningWithTrustedPrincipals())
|
||||
++sSetProtoCalled;
|
||||
|
||||
/* ECMAScript 5 8.6.2 forbids changing [[Prototype]] if not [[Extensible]]. */
|
||||
if (!obj->isExtensible()) {
|
||||
obj->reportNotExtensible(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!vp->isObjectOrNull())
|
||||
return true;
|
||||
|
||||
RootedObject pobj(cx, vp->toObjectOrNull());
|
||||
unsigned attrs;
|
||||
RootedId nid(cx, NameToId(cx->runtime->atomState.protoAtom));
|
||||
if (!CheckAccess(cx, obj, nid, JSAccessMode(JSACC_PROTO|JSACC_WRITE), vp, &attrs))
|
||||
return false;
|
||||
|
||||
return SetProto(cx, obj, pobj, true);
|
||||
}
|
||||
|
||||
#else /* !JS_HAS_OBJ_PROTO_PROP */
|
||||
|
||||
#define object_props NULL
|
||||
|
||||
#endif /* !JS_HAS_OBJ_PROTO_PROP */
|
||||
|
||||
static bool
|
||||
MarkSharpObjects(JSContext *cx, HandleObject obj, JSIdArray **idap, JSSharpInfo *value)
|
||||
{
|
||||
|
@ -1057,28 +1003,43 @@ obj_lookupSetter(JSContext *cx, unsigned argc, Value *vp)
|
|||
}
|
||||
#endif /* OLD_GETTER_SETTER_METHODS */
|
||||
|
||||
/* ES5 15.2.3.2. */
|
||||
JSBool
|
||||
obj_getPrototypeOf(JSContext *cx, unsigned argc, Value *vp)
|
||||
{
|
||||
if (argc == 0) {
|
||||
js_ReportMissingArg(cx, *vp, 0);
|
||||
return JS_FALSE;
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
/* Step 1. */
|
||||
if (args.length() == 0) {
|
||||
js_ReportMissingArg(cx, args.calleev(), 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vp[2].isPrimitive()) {
|
||||
if (args[0].isPrimitive()) {
|
||||
char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, vp[2], NULL);
|
||||
if (!bytes)
|
||||
return JS_FALSE;
|
||||
return false;
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
|
||||
JSMSG_UNEXPECTED_TYPE, bytes, "not an object");
|
||||
JS_free(cx, bytes);
|
||||
return JS_FALSE;
|
||||
return false;
|
||||
}
|
||||
|
||||
JSObject *obj = &vp[2].toObject();
|
||||
unsigned attrs;
|
||||
RootedId nid(cx, NameToId(cx->runtime->atomState.protoAtom));
|
||||
return CheckAccess(cx, obj, nid, JSACC_PROTO, vp, &attrs);
|
||||
/* Step 2. */
|
||||
|
||||
/*
|
||||
* Implement [[Prototype]]-getting -- particularly across compartment
|
||||
* boundaries -- by calling a cached __proto__ getter function.
|
||||
*/
|
||||
InvokeArgsGuard nested;
|
||||
if (!cx->stack.pushInvokeArgs(cx, 0, &nested))
|
||||
return false;
|
||||
nested.calleev() = cx->global()->protoGetter();
|
||||
nested.thisv() = args[0];
|
||||
if (!Invoke(cx, nested))
|
||||
return false;
|
||||
args.rval() = nested.rval();
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace js {
|
||||
|
|
|
@ -1062,12 +1062,6 @@ js_HasOwnProperty(JSContext *cx, js::LookupGenericOp lookup, js::HandleObject ob
|
|||
extern JSBool
|
||||
js_PropertyIsEnumerable(JSContext *cx, js::HandleObject obj, js::HandleId id, js::Value *vp);
|
||||
|
||||
#if JS_HAS_OBJ_PROTO_PROP
|
||||
extern JSPropertySpec object_props[];
|
||||
#else
|
||||
#define object_props NULL
|
||||
#endif
|
||||
|
||||
extern JSFunctionSpec object_methods[];
|
||||
extern JSFunctionSpec object_static_methods[];
|
||||
|
||||
|
|
|
@ -751,6 +751,8 @@ class ScriptedProxyHandler : public IndirectProxyHandler {
|
|||
virtual bool keys(JSContext *cx, JSObject *proxy, AutoIdVector &props);
|
||||
virtual bool iterate(JSContext *cx, JSObject *proxy, unsigned flags, Value *vp);
|
||||
|
||||
/* Spidermonkey extensions. */
|
||||
virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl, CallArgs args) MOZ_OVERRIDE;
|
||||
virtual JSType typeOf(JSContext *cx, JSObject *proxy);
|
||||
virtual bool defaultValue(JSContext *cx, JSObject *obj, JSType hint, Value *vp);
|
||||
|
||||
|
@ -960,6 +962,14 @@ ScriptedProxyHandler::iterate(JSContext *cx, JSObject *proxy_, unsigned flags, V
|
|||
ReturnedValueMustNotBePrimitive(cx, proxy, ATOM(iterate), *vp);
|
||||
}
|
||||
|
||||
bool
|
||||
ScriptedProxyHandler::nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
|
||||
CallArgs args)
|
||||
{
|
||||
return BaseProxyHandler::nativeCall(cx, test, impl, args);
|
||||
}
|
||||
|
||||
|
||||
JSType
|
||||
ScriptedProxyHandler::typeOf(JSContext *cx, JSObject *proxy)
|
||||
{
|
||||
|
|
|
@ -550,49 +550,6 @@ ArrayBufferObject::obj_setGeneric(JSContext *cx, HandleObject obj, HandleId id,
|
|||
if (!delegate)
|
||||
return false;
|
||||
|
||||
if (JSID_IS_ATOM(id, cx->runtime->atomState.protoAtom)) {
|
||||
// setting __proto__ = null
|
||||
// effectively removes the prototype chain.
|
||||
// any attempt to set __proto__ on native
|
||||
// objects after setting them to null makes
|
||||
// __proto__ just a plain property.
|
||||
// the following code simulates this behaviour on arrays.
|
||||
//
|
||||
// we first attempt to set the prototype on
|
||||
// the delegate which is a native object
|
||||
// so that existing code handles the case
|
||||
// of treating it as special or plain.
|
||||
// if the delegate's prototype has now changed
|
||||
// then we change our prototype too.
|
||||
//
|
||||
// otherwise __proto__ was a plain property
|
||||
// and we don't modify our prototype chain
|
||||
// since obj_getProperty will fetch it as a plain
|
||||
// property from the delegate.
|
||||
|
||||
RootedObject oldDelegateProto(cx, delegate->getProto());
|
||||
|
||||
if (!baseops::SetPropertyHelper(cx, delegate, delegate, id, 0, vp, strict))
|
||||
return false;
|
||||
|
||||
if (delegate->getProto() != oldDelegateProto) {
|
||||
// actual __proto__ was set and not a plain property called
|
||||
// __proto__
|
||||
if (!obj->isExtensible()) {
|
||||
obj->reportNotExtensible(cx);
|
||||
return false;
|
||||
}
|
||||
Rooted<JSObject*> newProto(cx, vp->toObjectOrNull());
|
||||
if (!SetProto(cx, obj, newProto, true)) {
|
||||
// this can be caused for example by setting x.__proto__ = x
|
||||
// restore delegate prototype chain
|
||||
SetProto(cx, delegate, oldDelegateProto, true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return baseops::SetPropertyHelper(cx, delegate, obj, id, 0, vp, strict);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/licenses/publicdomain/
|
||||
*/
|
||||
|
||||
var gTestfile = '__proto__.js';
|
||||
var BUGNUMBER = 770344;
|
||||
var summary = "__proto__ as accessor";
|
||||
|
||||
print(BUGNUMBER + ": " + summary);
|
||||
|
||||
/**************
|
||||
* BEGIN TEST *
|
||||
**************/
|
||||
|
||||
var protoDesc = Object.getOwnPropertyDescriptor(Object.prototype, "__proto__");
|
||||
assertEq(protoDesc !== null, true);
|
||||
assertEq(typeof protoDesc, "object");
|
||||
assertEq(protoDesc.hasOwnProperty("get"), true);
|
||||
assertEq(protoDesc.hasOwnProperty("set"), true);
|
||||
assertEq(protoDesc.hasOwnProperty("enumerable"), true);
|
||||
assertEq(protoDesc.hasOwnProperty("configurable"), true);
|
||||
assertEq(protoDesc.hasOwnProperty("value"), false);
|
||||
assertEq(protoDesc.hasOwnProperty("writable"), false);
|
||||
|
||||
assertEq(protoDesc.configurable, true);
|
||||
assertEq(protoDesc.enumerable, false);
|
||||
assertEq(typeof protoDesc.get, "function", protoDesc.get + "");
|
||||
assertEq(typeof protoDesc.set, "function", protoDesc.set + "");
|
||||
|
||||
assertEq(delete Object.prototype.__proto__, true);
|
||||
assertEq(Object.getOwnPropertyDescriptor(Object.prototype, "__proto__"),
|
||||
undefined);
|
||||
|
||||
var obj = {};
|
||||
obj.__proto__ = 5;
|
||||
assertEq(Object.getPrototypeOf(obj), Object.prototype);
|
||||
assertEq(obj.hasOwnProperty("__proto__"), true);
|
||||
|
||||
var desc = Object.getOwnPropertyDescriptor(obj, "__proto__");
|
||||
assertEq(desc !== null, true);
|
||||
assertEq(typeof desc, "object");
|
||||
assertEq(desc.value, 5);
|
||||
assertEq(desc.writable, true);
|
||||
assertEq(desc.enumerable, true);
|
||||
assertEq(desc.configurable, true);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
||||
|
||||
print("Tests complete");
|
|
@ -0,0 +1,54 @@
|
|||
// |reftest| skip-if(!xulRuntime.shell) -- needs newGlobal()
|
||||
/*
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/licenses/publicdomain/
|
||||
*/
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
var BUGNUMBER = 770344;
|
||||
var summary = "Object.getPrototypeOf behavior across compartments";
|
||||
|
||||
print(BUGNUMBER + ": " + summary);
|
||||
|
||||
/**************
|
||||
* BEGIN TEST *
|
||||
**************/
|
||||
|
||||
var other = newGlobal();
|
||||
|
||||
var getProto = Object.getPrototypeOf;
|
||||
var otherGetProto = other.Object.getPrototypeOf;
|
||||
|
||||
var proto = {};
|
||||
var obj = Object.create(proto);
|
||||
assertEq(getProto(obj), proto);
|
||||
assertEq(otherGetProto(obj), proto);
|
||||
|
||||
other.proto = proto;
|
||||
var otherObj = other.evaluate("Object.create(proto)");
|
||||
assertEq(getProto(otherObj), proto);
|
||||
assertEq(otherGetProto(otherObj), proto);
|
||||
|
||||
var p = other.evaluate("({})");
|
||||
var objOtherProto = Object.create(p);
|
||||
assertEq(getProto(objOtherProto), p);
|
||||
assertEq(otherGetProto(objOtherProto), p);
|
||||
|
||||
other.evaluate("var otherProto = { otherProto: 1 }; " +
|
||||
"var otherObj = Object.create(otherProto);");
|
||||
assertEq(getProto(other.otherObj), other.otherProto);
|
||||
assertEq(otherGetProto(other.otherObj), other.otherProto);
|
||||
|
||||
other.evaluate("var newOtherProto = { newOtherProto: 1 }; " +
|
||||
"otherObj.__proto__ = newOtherProto;");
|
||||
assertEq(otherGetProto(other.otherObj), other.newOtherProto);
|
||||
Math.sin();
|
||||
assertEq(getProto(other.otherObj), other.newOtherProto);
|
||||
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
||||
|
||||
print("Tests complete");
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/licenses/publicdomain/
|
||||
*/
|
||||
|
||||
var gTestfile = 'proxy-__proto__.js';
|
||||
var BUGNUMBER = 770344;
|
||||
var summary = "Behavior of __proto__ on proxies";
|
||||
|
||||
print(BUGNUMBER + ": " + summary);
|
||||
|
||||
/**************
|
||||
* BEGIN TEST *
|
||||
**************/
|
||||
|
||||
var protoDesc = Object.getOwnPropertyDescriptor(Object.prototype, "__proto__");
|
||||
var protoGetter = protoDesc.get;
|
||||
var protoSetter = protoDesc.set;
|
||||
|
||||
function pp(arr)
|
||||
{
|
||||
return arr.map(function(v) { return "" + v; }).join(", ");
|
||||
}
|
||||
|
||||
function testProxy(creator, args, proto)
|
||||
{
|
||||
print("Now testing behavior for " +
|
||||
"Proxy." + creator + "(" + pp(args) + ")");
|
||||
|
||||
var pobj = Proxy[creator].apply(Proxy, args);
|
||||
|
||||
// Check [[Prototype]] before attempted mutation
|
||||
assertEq(Object.getPrototypeOf(pobj), proto);
|
||||
assertEq(protoGetter.call(pobj), proto);
|
||||
|
||||
// Attempt [[Prototype]] mutation
|
||||
try
|
||||
{
|
||||
protoSetter.call(pobj);
|
||||
throw new Error("should throw trying to mutate a proxy's [[Prototype]]");
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
assertEq(e instanceof TypeError, true,
|
||||
"expected TypeError, instead got: " + e);
|
||||
}
|
||||
|
||||
// Check [[Prototype]] after attempted mutation
|
||||
assertEq(Object.getPrototypeOf(pobj), proto);
|
||||
assertEq(protoGetter.call(pobj), proto);
|
||||
}
|
||||
|
||||
// Proxy object with non-null [[Prototype]]
|
||||
var nonNullProto = { toString: function() { return "non-null prototype"; } };
|
||||
var nonNullHandler = { toString: function() { return "non-null handler"; } };
|
||||
testProxy("create", [nonNullHandler, nonNullProto], nonNullProto);
|
||||
|
||||
// Proxy object with null [[Prototype]]
|
||||
var nullProto = null;
|
||||
var nullHandler = { toString: function() { return "null handler"; } };
|
||||
testProxy("create", [nullHandler, nullProto], nullProto);
|
||||
|
||||
// Proxy function with [[Call]]
|
||||
var callForCallOnly = function () { };
|
||||
callForCallOnly.toString = function() { return "callForCallOnly"; };
|
||||
var callOnlyHandler = { toString: function() { return "call-only handler"; } };
|
||||
testProxy("createFunction",
|
||||
[callOnlyHandler, callForCallOnly], Function.prototype);
|
||||
|
||||
// Proxy function with [[Call]] and [[Construct]]
|
||||
var callForCallConstruct = function() { };
|
||||
callForCallConstruct.toString = function() { return "call/construct call"; };
|
||||
var constructForCallConstruct = function() { };
|
||||
constructForCallConstruct.toString =
|
||||
function() { return "call/construct construct"; };
|
||||
var handlerForCallConstruct =
|
||||
{ toString: function() { return "call/construct handler"; } };
|
||||
testProxy("createFunction",
|
||||
[handlerForCallConstruct,
|
||||
callForCallConstruct,
|
||||
constructForCallConstruct],
|
||||
Function.prototype);
|
||||
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
||||
|
||||
print("Tests complete");
|
|
@ -11,18 +11,11 @@ var expect = 'No Crash';
|
|||
|
||||
printBugNumber(BUGNUMBER);
|
||||
printStatus (summary);
|
||||
try
|
||||
{
|
||||
this.__proto__ = [];
|
||||
this.unwatch("x");
|
||||
}
|
||||
catch(ex)
|
||||
{
|
||||
print(ex + '');
|
||||
if (typeof window != 'undefined')
|
||||
{
|
||||
expect = 'Error: invalid __proto__ value (can only be set to null)';
|
||||
}
|
||||
actual = ex + '';
|
||||
}
|
||||
reportCompare(expect, actual, summary);
|
||||
|
||||
this.__proto__ = [];
|
||||
this.unwatch("x");
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
||||
|
||||
print("Tests complete");
|
||||
|
|
|
@ -11,11 +11,6 @@ print(BUGNUMBER + ": " + summary);
|
|||
* BEGIN TEST *
|
||||
**************/
|
||||
|
||||
var x = { prop: "value" };
|
||||
var a = new ArrayBuffer([]);
|
||||
a.__proto__ = x;
|
||||
assertEq(a.prop, "value");
|
||||
|
||||
ArrayBuffer.prototype.prop = "on prototype";
|
||||
var b = new ArrayBuffer([]);
|
||||
assertEq(b.prop, "on prototype");
|
||||
|
@ -25,6 +20,9 @@ assertEq(c.prop, "on prototype");
|
|||
c.prop = "direct";
|
||||
assertEq(c.prop, "direct");
|
||||
|
||||
assertEq(ArrayBuffer.prototype.prop, "on prototype");
|
||||
assertEq(new ArrayBuffer([]).prop, "on prototype");
|
||||
|
||||
assertEq(c.nonexistent, undefined);
|
||||
|
||||
reportCompare(true, true);
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
var x = new ArrayBuffer(2);
|
||||
|
||||
var test = function() {
|
||||
var test = function(newProto) {
|
||||
try {
|
||||
x.__proto__ = x;
|
||||
x.__proto__ = newProto;
|
||||
return false;
|
||||
} catch(e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
assertEq(test(), true);
|
||||
assertEq(test(x), true);
|
||||
assertEq(test({}), true);
|
||||
assertEq(test(null), true);
|
||||
|
||||
// ArrayBuffer's __proto__ behaviour verification.
|
||||
var y = new ArrayBuffer();
|
||||
y.__proto__ = null;
|
||||
assertEq(y.__proto__, undefined);
|
||||
reportCompare(true, true);
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
var x = new ArrayBuffer();
|
||||
// first set to null should make the proto chain undefined
|
||||
x.__proto__ = null;
|
||||
assertEq(x.__proto__, undefined);
|
||||
// second set makes it a property
|
||||
x.__proto__ = null;
|
||||
assertEq(x.__proto__, null);
|
||||
// always act as a property now
|
||||
x.__proto__ = {a:2};
|
||||
assertEq(x.__proto__.a, 2);
|
||||
assertEq(x.a, undefined);
|
||||
|
||||
var ab = new ArrayBuffer();
|
||||
// not the same as setting __proto__ to null
|
||||
ab.__proto__ = Object.create(null);
|
||||
ab.__proto__ = {a:2};
|
||||
// should still act like __proto__ is a plain property
|
||||
assertEq(ab.a, undefined);
|
||||
reportCompare(true, true);
|
|
@ -206,6 +206,13 @@ GlobalObject::createArrayFromBuffer<uint8_clamped>() const
|
|||
return createArrayFromBufferHelper(FROM_BUFFER_UINT8CLAMPED);
|
||||
}
|
||||
|
||||
void
|
||||
GlobalObject::setProtoGetter(JSFunction *protoGetter)
|
||||
{
|
||||
JS_ASSERT(getSlotRef(PROTO_GETTER).isUndefined());
|
||||
setSlot(PROTO_GETTER, ObjectValue(*protoGetter));
|
||||
}
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "jscntxt.h"
|
||||
#include "jsdate.h"
|
||||
#include "jsexn.h"
|
||||
#include "jsfriendapi.h"
|
||||
#include "jsmath.h"
|
||||
#include "json.h"
|
||||
#include "jsweakmap.h"
|
||||
|
@ -18,10 +19,10 @@
|
|||
#include "builtin/MapObject.h"
|
||||
#include "builtin/RegExp.h"
|
||||
#include "frontend/BytecodeEmitter.h"
|
||||
#include "vm/GlobalObject-inl.h"
|
||||
|
||||
#include "jsobjinlines.h"
|
||||
|
||||
#include "vm/GlobalObject-inl.h"
|
||||
#include "vm/RegExpObject-inl.h"
|
||||
#include "vm/RegExpStatics-inl.h"
|
||||
|
||||
|
@ -57,6 +58,121 @@ ThrowTypeError(JSContext *cx, unsigned argc, Value *vp)
|
|||
|
||||
namespace js {
|
||||
|
||||
static bool
|
||||
TestProtoGetterThis(const Value &v)
|
||||
{
|
||||
return !v.isNullOrUndefined();
|
||||
}
|
||||
|
||||
static bool
|
||||
ProtoGetterImpl(JSContext *cx, CallArgs args)
|
||||
{
|
||||
JS_ASSERT(TestProtoGetterThis(args.thisv()));
|
||||
|
||||
const Value &thisv = args.thisv();
|
||||
if (thisv.isPrimitive() && !BoxNonStrictThis(cx, args))
|
||||
return false;
|
||||
|
||||
unsigned dummy;
|
||||
Rooted<JSObject*> obj(cx, &args.thisv().toObject());
|
||||
Rooted<jsid> nid(cx, NameToId(cx->runtime->atomState.protoAtom));
|
||||
Rooted<Value> v(cx);
|
||||
if (!CheckAccess(cx, obj, nid, JSACC_PROTO, v.address(), &dummy))
|
||||
return false;
|
||||
|
||||
args.rval() = v;
|
||||
return true;
|
||||
}
|
||||
|
||||
static JSBool
|
||||
ProtoGetter(JSContext *cx, unsigned argc, Value *vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod(cx, TestProtoGetterThis, ProtoGetterImpl, args);
|
||||
}
|
||||
|
||||
size_t sSetProtoCalled = 0;
|
||||
|
||||
static bool
|
||||
TestProtoSetterThis(const Value &v)
|
||||
{
|
||||
if (v.isNullOrUndefined())
|
||||
return false;
|
||||
|
||||
/* These will work as if on a boxed primitive; dumb, but whatever. */
|
||||
if (!v.isObject())
|
||||
return true;
|
||||
|
||||
/* Otherwise, only accept non-proxies. */
|
||||
return !v.toObject().isProxy();
|
||||
}
|
||||
|
||||
static bool
|
||||
ProtoSetterImpl(JSContext *cx, CallArgs args)
|
||||
{
|
||||
JS_ASSERT(TestProtoSetterThis(args.thisv()));
|
||||
|
||||
const Value &thisv = args.thisv();
|
||||
if (thisv.isPrimitive()) {
|
||||
JS_ASSERT(!thisv.isNullOrUndefined());
|
||||
|
||||
// Mutating a boxed primitive's [[Prototype]] has no side effects.
|
||||
args.rval() = UndefinedValue();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!cx->runningWithTrustedPrincipals())
|
||||
++sSetProtoCalled;
|
||||
|
||||
Rooted<JSObject*> obj(cx, &args.thisv().toObject());
|
||||
|
||||
/* ES5 8.6.2 forbids changing [[Prototype]] if not [[Extensible]]. */
|
||||
if (!obj->isExtensible()) {
|
||||
obj->reportNotExtensible(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Disallow mutating the [[Prototype]] of a proxy that wasn't simply
|
||||
* wrapping some other object. Also disallow it on ArrayBuffer objects,
|
||||
* which due to their complicated delegate-object shenanigans can't easily
|
||||
* have a mutable [[Prototype]].
|
||||
*/
|
||||
if (obj->isProxy() || obj->isArrayBuffer()) {
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO,
|
||||
"Object", "__proto__ setter",
|
||||
obj->isProxy() ? "Proxy" : "ArrayBuffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Do nothing if __proto__ isn't being set to an object or null. */
|
||||
if (args.length() == 0 || !args[0].isObjectOrNull()) {
|
||||
args.rval() = UndefinedValue();
|
||||
return true;
|
||||
}
|
||||
|
||||
Rooted<JSObject*> newProto(cx, args[0].toObjectOrNull());
|
||||
|
||||
unsigned dummy;
|
||||
Rooted<jsid> nid(cx, NameToId(cx->runtime->atomState.protoAtom));
|
||||
Rooted<Value> v(cx);
|
||||
if (!CheckAccess(cx, obj, nid, JSAccessMode(JSACC_PROTO | JSACC_WRITE), v.address(), &dummy))
|
||||
return false;
|
||||
|
||||
if (!SetProto(cx, obj, newProto, true))
|
||||
return false;
|
||||
|
||||
args.rval() = UndefinedValue();
|
||||
return true;
|
||||
}
|
||||
|
||||
static JSBool
|
||||
ProtoSetter(JSContext *cx, unsigned argc, Value *vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod(cx, TestProtoSetterThis, ProtoSetterImpl, args);
|
||||
}
|
||||
|
||||
JSObject *
|
||||
GlobalObject::initFunctionAndObjectClasses(JSContext *cx)
|
||||
{
|
||||
|
@ -184,8 +300,36 @@ GlobalObject::initFunctionAndObjectClasses(JSContext *cx)
|
|||
* primordial values have.
|
||||
*/
|
||||
if (!LinkConstructorAndPrototype(cx, objectCtor, objectProto) ||
|
||||
!DefinePropertiesAndBrand(cx, objectProto, object_props, object_methods) ||
|
||||
!DefinePropertiesAndBrand(cx, objectCtor, NULL, object_static_methods) ||
|
||||
!DefinePropertiesAndBrand(cx, objectProto, NULL, object_methods))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add an Object.prototype.__proto__ accessor property to implement that
|
||||
* extension (if it's actually enabled). Cache the getter for this
|
||||
* function so that cross-compartment [[Prototype]]-getting is implemented
|
||||
* in one place.
|
||||
*/
|
||||
Rooted<JSFunction*> getter(cx, js_NewFunction(cx, NULL, ProtoGetter, 0, 0, self, NULL));
|
||||
if (!getter)
|
||||
return NULL;
|
||||
#if JS_HAS_OBJ_PROTO_PROP
|
||||
Rooted<JSFunction*> setter(cx, js_NewFunction(cx, NULL, ProtoSetter, 0, 0, self, NULL));
|
||||
if (!setter)
|
||||
return NULL;
|
||||
if (!objectProto->defineProperty(cx, cx->runtime->atomState.protoAtom, UndefinedValue(),
|
||||
JS_DATA_TO_FUNC_PTR(PropertyOp, getter.get()),
|
||||
JS_DATA_TO_FUNC_PTR(StrictPropertyOp, setter.get()),
|
||||
JSPROP_GETTER | JSPROP_SETTER | JSPROP_SHARED))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
#endif /* JS_HAS_OBJ_PROTO_PROP */
|
||||
self->setProtoGetter(getter);
|
||||
|
||||
|
||||
if (!DefinePropertiesAndBrand(cx, objectCtor, NULL, object_static_methods) ||
|
||||
!LinkConstructorAndPrototype(cx, functionCtor, functionProto) ||
|
||||
!DefinePropertiesAndBrand(cx, functionProto, NULL, function_methods) ||
|
||||
!DefinePropertiesAndBrand(cx, functionCtor, NULL, NULL))
|
||||
|
@ -315,14 +459,15 @@ GlobalObject::clear(JSContext *cx)
|
|||
setSlot(RUNTIME_CODEGEN_ENABLED, UndefinedValue());
|
||||
|
||||
/*
|
||||
* Clear all slots storing function values, in case throwing trying to
|
||||
* execute a script for this global must reinitialize standard classes.
|
||||
* See bug 470150.
|
||||
* Clear all slots storing values in case throwing trying to execute a
|
||||
* script for this global must reinitialize standard classes. See
|
||||
* bug 470150.
|
||||
*/
|
||||
setSlot(BOOLEAN_VALUEOF, UndefinedValue());
|
||||
setSlot(EVAL, UndefinedValue());
|
||||
setSlot(CREATE_DATAVIEW_FOR_THIS, UndefinedValue());
|
||||
setSlot(THROWTYPEERROR, UndefinedValue());
|
||||
setSlot(PROTO_GETTER, UndefinedValue());
|
||||
|
||||
/*
|
||||
* Mark global as cleared. If we try to execute any compile-and-go
|
||||
|
@ -420,11 +565,14 @@ LinkConstructorAndPrototype(JSContext *cx, JSObject *ctor_, JSObject *proto_)
|
|||
}
|
||||
|
||||
bool
|
||||
DefinePropertiesAndBrand(JSContext *cx, JSObject *obj_, JSPropertySpec *ps, JSFunctionSpec *fs)
|
||||
DefinePropertiesAndBrand(JSContext *cx, JSObject *obj_,
|
||||
const JSPropertySpec *ps, const JSFunctionSpec *fs)
|
||||
{
|
||||
RootedObject obj(cx, obj_);
|
||||
|
||||
if ((ps && !JS_DefineProperties(cx, obj, ps)) || (fs && !JS_DefineFunctions(cx, obj, fs)))
|
||||
if (ps && !JS_DefineProperties(cx, obj, const_cast<JSPropertySpec*>(ps)))
|
||||
return false;
|
||||
if (fs && !JS_DefineFunctions(cx, obj, const_cast<JSFunctionSpec*>(fs)))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -74,12 +74,13 @@ class GlobalObject : public JSObject
|
|||
static const unsigned EVAL = BOOLEAN_VALUEOF + 1;
|
||||
static const unsigned CREATE_DATAVIEW_FOR_THIS = EVAL + 1;
|
||||
static const unsigned THROWTYPEERROR = CREATE_DATAVIEW_FOR_THIS + 1;
|
||||
static const unsigned PROTO_GETTER = THROWTYPEERROR + 1;
|
||||
|
||||
/*
|
||||
* Instances of the internal createArrayFromBuffer function used by the
|
||||
* typed array code, one per typed array element type.
|
||||
*/
|
||||
static const unsigned FROM_BUFFER_UINT8 = THROWTYPEERROR + 1;
|
||||
static const unsigned FROM_BUFFER_UINT8 = PROTO_GETTER + 1;
|
||||
static const unsigned FROM_BUFFER_INT8 = FROM_BUFFER_UINT8 + 1;
|
||||
static const unsigned FROM_BUFFER_UINT16 = FROM_BUFFER_INT8 + 1;
|
||||
static const unsigned FROM_BUFFER_INT16 = FROM_BUFFER_UINT16 + 1;
|
||||
|
@ -129,6 +130,7 @@ class GlobalObject : public JSObject
|
|||
|
||||
inline void setThrowTypeError(JSFunction *fun);
|
||||
inline void setOriginalEval(JSObject *evalobj);
|
||||
inline void setProtoGetter(JSFunction *protoGetter);
|
||||
|
||||
Value getConstructor(JSProtoKey key) const {
|
||||
JS_ASSERT(key <= JSProto_LIMIT);
|
||||
|
@ -347,6 +349,11 @@ class GlobalObject : public JSObject
|
|||
template<typename T>
|
||||
inline Value createArrayFromBuffer() const;
|
||||
|
||||
Value protoGetter() const {
|
||||
JS_ASSERT(functionObjectClassesInitialized());
|
||||
return getSlot(PROTO_GETTER);
|
||||
}
|
||||
|
||||
void clear(JSContext *cx);
|
||||
|
||||
bool isCleared() const {
|
||||
|
@ -395,7 +402,8 @@ LinkConstructorAndPrototype(JSContext *cx, JSObject *ctor, JSObject *proto);
|
|||
* benefits.
|
||||
*/
|
||||
extern bool
|
||||
DefinePropertiesAndBrand(JSContext *cx, JSObject *obj, JSPropertySpec *ps, JSFunctionSpec *fs);
|
||||
DefinePropertiesAndBrand(JSContext *cx, JSObject *obj,
|
||||
const JSPropertySpec *ps, const JSFunctionSpec *fs);
|
||||
|
||||
typedef HashSet<GlobalObject *, DefaultHasher<GlobalObject *>, SystemAllocPolicy> GlobalObjectSet;
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче