Bug 966518 - Part 0: Make proxy callability into a trap, rather than a class check. (r=bholley, r=djvj, r=peterv)

This commit is contained in:
Eric Faust 2014-09-10 15:52:36 -07:00
parent 81796d368a
commit 404a29c4f3
38 changed files with 346 additions and 138 deletions

View File

@ -642,6 +642,12 @@ public:
JS::Handle<jsid> id, JS::Handle<JSObject*> callable) const MOZ_OVERRIDE;
virtual bool unwatch(JSContext *cx, JS::Handle<JSObject*> proxy,
JS::Handle<jsid> id) const MOZ_OVERRIDE;
virtual bool isCallable(JSObject *obj) const MOZ_OVERRIDE {
return false;
}
virtual bool isConstructor(JSObject *obj) const MOZ_OVERRIDE {
return false;
}
// Derived traps
virtual bool has(JSContext *cx, JS::Handle<JSObject*> proxy,
@ -694,8 +700,6 @@ const js::Class OuterWindowProxyClass =
"Proxy",
0, /* additional slots */
0, /* additional class flags */
nullptr, /* call */
nullptr, /* construct */
PROXY_MAKE_EXT(
nullptr, /* outerObject */
js::proxy_innerObject,
@ -1055,7 +1059,7 @@ NewOuterWindowProxy(JSContext *cx, JS::Handle<JSObject*> parent, bool isChrome)
JSObject *obj = js::Wrapper::New(cx, parent, parent,
isChrome ? &nsChromeOuterWindowProxy::singleton
: &nsOuterWindowProxy::singleton,
&options);
options);
NS_ASSERTION(js::GetObjectClass(obj)->ext.innerObject, "bad class");
return obj;

View File

@ -481,21 +481,17 @@ class CGDOMProxyJSClass(CGThing):
# HTMLAllCollection. So just hardcode it here.
if self.descriptor.interface.identifier.name == "HTMLAllCollection":
flags.append("JSCLASS_EMULATES_UNDEFINED")
callHook = LEGACYCALLER_HOOK_NAME if self.descriptor.operations["LegacyCaller"] else 'nullptr'
return fill(
"""
static const DOMJSClass Class = {
PROXY_CLASS_DEF("${name}",
0, /* extra slots */
${flags},
${call}, /* call */
nullptr /* construct */),
${flags}),
$*{descriptor}
};
""",
name=self.descriptor.interface.identifier.name,
flags=" | ".join(flags),
call=callHook,
descriptor=DOMClass(self.descriptor))
@ -10641,6 +10637,33 @@ class CGDOMJSProxyHandler_getInstance(ClassMethod):
""")
class CGDOMJSProxyHandler_call(ClassMethod):
def __init__(self):
args = [Argument('JSContext*', 'cx'),
Argument('JS::Handle<JSObject*>', 'proxy'),
Argument('const JS::CallArgs&', 'args')]
ClassMethod.__init__(self, "call", "bool", args, virtual=True, override=True, const=True)
def getBody(self):
return fill(
"""
return js::ForwardToNative(cx, ${legacyCaller}, args);
""",
legacyCaller=LEGACYCALLER_HOOK_NAME)
class CGDOMJSProxyHandler_isCallable(ClassMethod):
def __init__(self):
ClassMethod.__init__(self, "isCallable", "bool", [Argument('JSObject*', 'obj')],
virtual=True, override=True, const=True)
def getBody(self):
return dedent("""
return true;
""")
class CGDOMJSProxyHandler(CGClass):
def __init__(self, descriptor):
assert (descriptor.supportsIndexedProperties() or
@ -10672,6 +10695,9 @@ class CGDOMJSProxyHandler(CGClass):
(descriptor.operations['NamedSetter'] is not None and
descriptor.interface.getExtendedAttribute('OverrideBuiltins'))):
methods.append(CGDOMJSProxyHandler_setCustom(descriptor))
if descriptor.operations['LegacyCaller']:
methods.append(CGDOMJSProxyHandler_call())
methods.append(CGDOMJSProxyHandler_isCallable())
CGClass.__init__(self, 'DOMProxyHandler',
bases=[ClassBase('mozilla::dom::DOMProxyHandler')],

View File

@ -82,6 +82,8 @@ class CPOWProxyHandler : public BaseProxyHandler
JSContext *cx) const MOZ_OVERRIDE;
virtual const char* className(JSContext *cx, HandleObject proxy) const MOZ_OVERRIDE;
virtual void finalize(JSFreeOp *fop, JSObject *proxy) const MOZ_OVERRIDE;
virtual bool isCallable(JSObject *obj) const MOZ_OVERRIDE;
virtual bool isConstructor(JSObject *obj) const MOZ_OVERRIDE;
static const char family;
static const CPOWProxyHandler singleton;
@ -620,6 +622,25 @@ CPOWProxyHandler::finalize(JSFreeOp *fop, JSObject *proxy) const
OwnerOf(proxy)->drop(proxy);
}
bool
CPOWProxyHandler::isCallable(JSObject *obj) const
{
return OwnerOf(obj)->isCallable(obj);
}
bool
CPOWProxyHandler::isConstructor(JSObject *obj) const
{
return isCallable(obj);
}
bool
WrapperOwner::isCallable(JSObject *obj)
{
ObjectId objId = idOf(obj);
return !!(objId & OBJECT_IS_CALLABLE);
}
void
WrapperOwner::drop(JSObject *obj)
{
@ -837,19 +858,14 @@ WrapperOwner::fromRemoteObjectVariant(JSContext *cx, RemoteObject objVar)
return nullptr;
}
bool callable = !!(objId & OBJECT_IS_CALLABLE);
RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
RootedValue v(cx, UndefinedValue());
ProxyOptions options;
options.selectDefaultClass(callable);
obj = NewProxyObject(cx,
&CPOWProxyHandler::singleton,
v,
nullptr,
global,
options);
global);
if (!obj)
return nullptr;

View File

@ -59,6 +59,8 @@ class WrapperOwner : public virtual JavaScriptShared
bool construct);
bool objectClassIs(JSContext *cx, JS::HandleObject obj, js::ESClassValue classValue);
const char* className(JSContext *cx, JS::HandleObject proxy);
bool isCallable(JSObject *obj);
// isConstructable is implemented here as isCallable.
nsresult instanceOf(JSObject *obj, const nsID *id, bool *bp);

View File

@ -482,7 +482,8 @@ struct Class
return flags & JSCLASS_EMULATES_UNDEFINED;
}
bool isCallable() const {
bool nonProxyCallable() const {
MOZ_ASSERT(!isProxy());
return this == js::FunctionClassPtr || call;
}

View File

@ -8764,16 +8764,40 @@ CodeGenerator::visitProfilerStackOp(LProfilerStackOp *lir)
}
}
class OutOfLineIsCallable : public OutOfLineCodeBase<CodeGenerator>
{
LIsCallable *ins_;
public:
OutOfLineIsCallable(LIsCallable *ins)
: ins_(ins)
{ }
bool accept(CodeGenerator *codegen) {
return codegen->visitOutOfLineIsCallable(this);
}
LIsCallable *ins() const {
return ins_;
}
};
bool
CodeGenerator::visitIsCallable(LIsCallable *ins)
{
Register object = ToRegister(ins->object());
Register output = ToRegister(ins->output());
OutOfLineIsCallable *ool = new(alloc()) OutOfLineIsCallable(ins);
if (!addOutOfLineCode(ool, ins->mir()))
return false;
Label notFunction, done;
masm.loadObjClass(object, output);
// An object is callable iff (is<JSFunction>() || getClass()->call).
Label notFunction, done, notCall;
// Just skim proxies off. Their notion of isCallable() is more complicated.
masm.branchTestClassIsProxy(true, output, ool->entry());
// An object is callable iff (is<JSFunction>() || getClass()->call.
masm.branchPtr(Assembler::NotEqual, output, ImmPtr(&JSFunction::class_), &notFunction);
masm.move32(Imm32(1), output);
masm.jump(&done);
@ -8781,6 +8805,28 @@ CodeGenerator::visitIsCallable(LIsCallable *ins)
masm.bind(&notFunction);
masm.cmpPtrSet(Assembler::NonZero, Address(output, offsetof(js::Class, call)), ImmPtr(nullptr), output);
masm.bind(&done);
masm.bind(ool->rejoin());
return true;
}
bool
CodeGenerator::visitOutOfLineIsCallable(OutOfLineIsCallable *ool)
{
LIsCallable *ins = ool->ins();
Register object = ToRegister(ins->object());
Register output = ToRegister(ins->output());
saveVolatile(output);
masm.setupUnalignedABICall(1, output);
masm.passABIArg(object);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, ObjectIsCallable));
masm.storeCallResult(output);
// C++ compilers like to only use the bottom byte for bools, but we need to maintain the entire
// register.
masm.and32(Imm32(0xFF), output);
restoreVolatile(output);
masm.jump(ool->rejoin());
return true;
}

View File

@ -43,6 +43,7 @@ class OutOfLineLoadTypedArray;
class OutOfLineNewGCThingPar;
class OutOfLineUpdateCache;
class OutOfLineCallPostWriteBarrier;
class OutOfLineIsCallable;
class CodeGenerator : public CodeGeneratorSpecific
{
@ -296,6 +297,7 @@ class CodeGenerator : public CodeGeneratorSpecific
bool visitCallDOMNative(LCallDOMNative *lir);
bool visitCallGetIntrinsicValue(LCallGetIntrinsicValue *lir);
bool visitIsCallable(LIsCallable *lir);
bool visitOutOfLineIsCallable(OutOfLineIsCallable *ool);
bool visitIsObject(LIsObject *lir);
bool visitHaveSameClass(LHaveSameClass *lir);
bool visitHasClass(LHasClass *lir);

View File

@ -1567,16 +1567,6 @@ GetPropertyIC::tryAttachProxy(JSContext *cx, HandleScript outerScript, IonScript
return tryAttachGenericProxy(cx, outerScript, ion, obj, name, returnAddr, emitted);
}
static void
GenerateProxyClassGuards(MacroAssembler &masm, Register object, Register scratchReg,
Label *failures)
{
masm.loadObjClass(object, scratchReg);
masm.branchTest32(Assembler::Zero,
Address(scratchReg, Class::offsetOfFlags()),
Imm32(JSCLASS_IS_PROXY), failures);
}
bool
GetPropertyIC::tryAttachGenericProxy(JSContext *cx, HandleScript outerScript, IonScript *ion,
HandleObject obj, HandlePropertyName name, void *returnAddr,
@ -1602,7 +1592,7 @@ GetPropertyIC::tryAttachGenericProxy(JSContext *cx, HandleScript outerScript, Io
Register scratchReg = output().valueReg().scratchReg();
GenerateProxyClassGuards(masm, object(), scratchReg, &failures);
masm.branchTestObjectIsProxy(false, object(), scratchReg, &failures);
// Ensure that the incoming object is not a DOM proxy, so that we can get to
// the specialized stubs
@ -2181,7 +2171,7 @@ SetPropertyIC::attachGenericProxy(JSContext *cx, HandleScript outerScript, IonSc
Register scratch = regSet.takeGeneral();
masm.push(scratch);
GenerateProxyClassGuards(masm, object(), scratch, &proxyFailures);
masm.branchTestObjectIsProxy(false, object(), scratch, &proxyFailures);
// Remove the DOM proxies. They'll take care of themselves so this stub doesn't
// catch too much. The failure case is actually Equal. Fall through to the failure code.

View File

@ -982,12 +982,25 @@ class MacroAssembler : public MacroAssemblerSpecific
loadObjClass(objReg, scratch);
Address flags(scratch, Class::offsetOfFlags());
branchTest32(Assembler::NonZero, flags, Imm32(JSCLASS_IS_PROXY), slowCheck);
branchTestClassIsProxy(true, scratch, slowCheck);
Condition cond = truthy ? Assembler::Zero : Assembler::NonZero;
branchTest32(cond, flags, Imm32(JSCLASS_EMULATES_UNDEFINED), checked);
}
void branchTestClassIsProxy(bool proxy, Register clasp, Label *label)
{
branchTest32(proxy ? Assembler::NonZero : Assembler::Zero,
Address(clasp, Class::offsetOfFlags()),
Imm32(JSCLASS_IS_PROXY), label);
}
void branchTestObjectIsProxy(bool proxy, Register object, Register scratch, Label *label)
{
loadObjClass(object, scratch);
branchTestClassIsProxy(proxy, scratch, label);
}
private:
// These two functions are helpers used around call sites throughout the
// assembler. They are called from the above call wrappers to emit the

View File

@ -1982,9 +1982,9 @@ IonBuilder::inlineIsCallable(CallInfo &callInfo)
} else {
types::TemporaryTypeSet *types = callInfo.getArg(0)->resultTypeSet();
const Class *clasp = types ? types->getKnownClass() : nullptr;
if (clasp) {
if (clasp && !clasp->isProxy()) {
isCallableKnown = true;
isCallableConstant = clasp->isCallable();
isCallableConstant = clasp->nonProxyCallable();
}
}

View File

@ -1239,6 +1239,12 @@ TypedObjectProto(JSObject *obj)
return &typedObj.typedProto();
}
bool
ObjectIsCallable(JSObject *obj)
{
return obj->isCallable();
}
void
MarkValueFromIon(JSRuntime *rt, Value *vp)
{

View File

@ -747,6 +747,8 @@ IonMarkFunction(MIRType type)
}
}
bool ObjectIsCallable(JSObject *obj);
} // namespace jit
} // namespace js

View File

@ -19,8 +19,6 @@ const js::Class OuterWrapperClass =
"Proxy",
0, /* additional slots */
0, /* additional class flags */
nullptr, /* call */
nullptr, /* construct */
PROXY_MAKE_EXT(
nullptr, /* outerObject */
js::proxy_innerObject,
@ -63,7 +61,7 @@ BEGIN_TEST(testBug604087)
js::WrapperOptions options;
options.setClass(&OuterWrapperClass);
options.setSingleton(true);
JS::RootedObject outerObj(cx, js::Wrapper::New(cx, global, global, &js::Wrapper::singleton, &options));
JS::RootedObject outerObj(cx, js::Wrapper::New(cx, global, global, &js::Wrapper::singleton, options));
JS::RootedObject compartment2(cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr, JS::FireOnNewGlobalHook));
JS::RootedObject compartment3(cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr, JS::FireOnNewGlobalHook));
JS::RootedObject compartment4(cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr, JS::FireOnNewGlobalHook));
@ -84,7 +82,7 @@ BEGIN_TEST(testBug604087)
JS::RootedObject next(cx);
{
JSAutoCompartment ac(cx, compartment2);
next = js::Wrapper::New(cx, compartment2, compartment2, &js::Wrapper::singleton, &options);
next = js::Wrapper::New(cx, compartment2, compartment2, &js::Wrapper::singleton, options);
CHECK(next);
}

View File

@ -284,7 +284,7 @@ CallJSNativeConstructor(JSContext *cx, Native native, const CallArgs &args)
*
* - (new Object(Object)) returns the callee.
*/
JS_ASSERT_IF(native != ProxyObject::callableClass_.construct &&
JS_ASSERT_IF(native != js::proxy_Construct &&
native != js::CallOrConstructBoundFunction &&
native != js::IteratorConstructor &&
(!callee->is<JSFunction>() || callee->as<JSFunction>().native() != obj_construct),

View File

@ -435,8 +435,8 @@ JSCompartment::wrap(JSContext *cx, MutableHandleObject obj, HandleObject existin
if (existing) {
// Is it possible to reuse |existing|?
if (!existing->getTaggedProto().isLazy() ||
// Note: don't use is<ObjectProxyObject>() here -- it also matches subclasses!
existing->getClass() != &ProxyObject::uncallableClass_ ||
// Note: Class asserted above, so all that's left to check is callability
existing->isCallable() ||
existing->getParent() != global ||
obj->isCallable())
{

View File

@ -1218,3 +1218,9 @@ JS_StoreStringPostBarrierCallback(JSContext* cx,
rt->gc.storeBuffer.putCallback(callback, key, data);
}
#endif /* JSGC_GENERATIONAL */
JS_FRIEND_API(bool)
js::ForwardToNative(JSContext *cx, JSNative native, const CallArgs &args)
{
return native(cx, args.length(), args.base());
}

View File

@ -266,7 +266,7 @@ namespace js {
js::proxy_ObjectMoved \
}
#define PROXY_CLASS_WITH_EXT(name, extraSlots, flags, callOp, constructOp, ext) \
#define PROXY_CLASS_WITH_EXT(name, extraSlots, flags, ext) \
{ \
name, \
js::Class::NON_NATIVE | \
@ -282,9 +282,9 @@ namespace js {
JS_ResolveStub, \
js::proxy_Convert, \
js::proxy_Finalize, /* finalize */ \
callOp, /* call */ \
nullptr, /* call */ \
js::proxy_HasInstance, /* hasInstance */ \
constructOp, /* construct */ \
nullptr, /* construct */ \
js::proxy_Trace, /* trace */ \
JS_NULL_CLASS_SPEC, \
ext, \
@ -311,8 +311,8 @@ namespace js {
} \
}
#define PROXY_CLASS_DEF(name, extraSlots, flags, callOp, constructOp) \
PROXY_CLASS_WITH_EXT(name, extraSlots, flags, callOp, constructOp, \
#define PROXY_CLASS_DEF(name, extraSlots, flags) \
PROXY_CLASS_WITH_EXT(name, extraSlots, flags, \
PROXY_MAKE_EXT( \
nullptr, /* outerObject */ \
nullptr, /* innerObject */ \
@ -2476,6 +2476,9 @@ JS_FRIEND_API(bool)
SliceSlowly(JSContext* cx, JS::HandleObject obj, JS::HandleObject receiver,
uint32_t begin, uint32_t end, JS::HandleObject result);
JS_FRIEND_API(bool)
ForwardToNative(JSContext *cx, JSNative native, const JS::CallArgs &args);
/* ES5 8.12.8. */
extern JS_FRIEND_API(bool)
DefaultValue(JSContext *cx, JS::HandleObject obj, JSType hint, JS::MutableHandleValue vp);

View File

@ -2183,7 +2183,7 @@ CheckIsValidConstructible(Value calleev)
if (callee->is<JSFunction>())
JS_ASSERT(callee->as<JSFunction>().isNativeConstructor());
else
JS_ASSERT(callee->getClass()->construct != nullptr);
JS_ASSERT(callee->constructHook() != nullptr);
}
} // namespace detail

View File

@ -2063,7 +2063,7 @@ TemporaryTypeSet::maybeCallable()
unsigned count = getObjectCount();
for (unsigned i = 0; i < count; i++) {
const Class *clasp = getObjectClass(i);
if (clasp && clasp->isCallable())
if (clasp && (clasp->isProxy() || clasp->nonProxyCallable()))
return true;
}

View File

@ -3794,6 +3794,14 @@ js::FindClassObject(ExclusiveContext *cx, MutableHandleObject protop, const Clas
return true;
}
bool
JSObject::isCallable() const
{
if (is<JSFunction>())
return true;
return callHook() != nullptr;
}
bool
JSObject::isConstructor() const
{
@ -3801,7 +3809,39 @@ JSObject::isConstructor() const
const JSFunction &fun = as<JSFunction>();
return fun.isNativeConstructor() || fun.isInterpretedConstructor();
}
return getClass()->construct != nullptr;
return constructHook() != nullptr;
}
JSNative
JSObject::callHook() const
{
const js::Class *clasp = getClass();
if (clasp->call)
return clasp->call;
if (is<js::ProxyObject>()) {
const js::ProxyObject &p = as<js::ProxyObject>();
if (p.handler()->isCallable(const_cast<JSObject*>(this)))
return js::proxy_Call;
}
return nullptr;
}
JSNative
JSObject::constructHook() const
{
const js::Class *clasp = getClass();
if (clasp->construct)
return clasp->construct;
if (is<js::ProxyObject>()) {
const js::ProxyObject &p = as<js::ProxyObject>();
if (p.handler()->isConstructor(const_cast<JSObject*>(this)))
return js::proxy_Construct;
}
return nullptr;
}
/* static */ bool

View File

@ -851,10 +851,10 @@ class JSObject : public js::ObjectImpl
/*
* Back to generic stuff.
*/
bool isCallable() {
return getClass()->isCallable();
}
bool isCallable() const;
bool isConstructor() const;
JSNative callHook() const;
JSNative constructHook() const;
inline void finish(js::FreeOp *fop);
MOZ_ALWAYS_INLINE void finalize(js::FreeOp *fop);

View File

@ -98,7 +98,7 @@
real(Float32Array, 28, js_InitViaClassSpec, TYPED_ARRAY_CLASP(Float32)) \
real(Float64Array, 29, js_InitViaClassSpec, TYPED_ARRAY_CLASP(Float64)) \
real(Uint8ClampedArray, 30, js_InitViaClassSpec, TYPED_ARRAY_CLASP(Uint8Clamped)) \
real(Proxy, 31, js_InitProxyClass, &ProxyObject::uncallableClass_) \
real(Proxy, 31, js_InitProxyClass, OCLASP(Proxy)) \
real(WeakMap, 32, js_InitWeakMapClass, OCLASP(WeakMap)) \
real(Map, 33, js_InitMapClass, OCLASP(Map)) \
real(Set, 34, js_InitSetClass, OCLASP(Set)) \

View File

@ -224,6 +224,13 @@ 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;
// Allow proxies, wrappers in particular, to specify callability at runtime.
// Note: These do not take const JSObject *, but they do in spirit.
// We are not prepared to do this, as there's little const correctness
// in the external APIs that handle proxies.
virtual bool isCallable(JSObject *obj) const;
virtual bool isConstructor(JSObject *obj) const;
// These two hooks must be overridden, or not overridden, in tandem -- no
// overriding just one!
virtual bool watch(JSContext *cx, JS::HandleObject proxy, JS::HandleId id,
@ -306,11 +313,10 @@ class JS_PUBLIC_API(DirectProxyHandler) : public BaseProxyHandler
RegExpGuard *g) const MOZ_OVERRIDE;
virtual bool boxedValue_unbox(JSContext *cx, HandleObject proxy, MutableHandleValue vp) const;
virtual JSObject *weakmapKeyDelegate(JSObject *proxy) const MOZ_OVERRIDE;
virtual bool isCallable(JSObject *obj) const MOZ_OVERRIDE;
};
// Use these in places where you don't want to #include vm/ProxyObject.h.
extern JS_FRIEND_DATA(const js::Class* const) CallableProxyClassPtr;
extern JS_FRIEND_DATA(const js::Class* const) UncallableProxyClassPtr;
extern JS_FRIEND_DATA(const js::Class* const) ProxyClassPtr;
inline bool IsProxy(JSObject *obj)
{
@ -382,14 +388,14 @@ IsScriptedProxy(JSObject *obj)
class MOZ_STACK_CLASS ProxyOptions {
protected:
/* protected constructor for subclass */
ProxyOptions(bool singletonArg, const Class *claspArg)
ProxyOptions(bool singletonArg)
: singleton_(singletonArg),
clasp_(claspArg)
clasp_(ProxyClassPtr)
{}
public:
ProxyOptions() : singleton_(false),
clasp_(UncallableProxyClassPtr)
clasp_(ProxyClassPtr)
{}
bool singleton() const { return singleton_; }
@ -405,11 +411,6 @@ class MOZ_STACK_CLASS ProxyOptions {
clasp_ = claspArg;
return *this;
}
ProxyOptions &selectDefaultClass(bool callable) {
const Class *classp = callable? CallableProxyClassPtr :
UncallableProxyClassPtr;
return setClass(classp);
}
private:
bool singleton_;

View File

@ -23,11 +23,11 @@ class DummyFrameGuard;
*/
class MOZ_STACK_CLASS WrapperOptions : public ProxyOptions {
public:
WrapperOptions() : ProxyOptions(false, nullptr),
WrapperOptions() : ProxyOptions(false),
proto_()
{}
explicit WrapperOptions(JSContext *cx) : ProxyOptions(false, nullptr),
explicit WrapperOptions(JSContext *cx) : ProxyOptions(false),
proto_()
{
proto_.emplace(cx);
@ -69,7 +69,7 @@ class JS_FRIEND_API(Wrapper) : public DirectProxyHandler
MutableHandleValue vp) const MOZ_OVERRIDE;
static JSObject *New(JSContext *cx, JSObject *obj, JSObject *parent, const Wrapper *handler,
const WrapperOptions *options = nullptr);
const WrapperOptions &options = WrapperOptions());
static JSObject *Renew(JSContext *cx, JSObject *existing, JSObject *obj, const Wrapper *handler);
@ -88,6 +88,7 @@ class JS_FRIEND_API(Wrapper) : public DirectProxyHandler
{ }
virtual bool finalizeInBackground(Value priv) const MOZ_OVERRIDE;
virtual bool isConstructor(JSObject *obj) const MOZ_OVERRIDE;
static const char family;
static const Wrapper singleton;
@ -198,6 +199,9 @@ class JS_FRIEND_API(SecurityWrapper) : public Base
JS::HandleObject callable) const MOZ_OVERRIDE;
virtual bool unwatch(JSContext *cx, JS::HandleObject proxy, JS::HandleId id) const MOZ_OVERRIDE;
// Allow isCallable and isConstructor. They used to be class-level, and so could not be guarded
// against.
/*
* Allow our subclasses to select the superclass behavior they want without
* needing to specify an exact superclass.

View File

@ -341,3 +341,15 @@ BaseProxyHandler::slice(JSContext *cx, HandleObject proxy, uint32_t begin, uint3
return js::SliceSlowly(cx, proxy, proxy, begin, end, result);
}
bool
BaseProxyHandler::isCallable(JSObject *obj) const
{
return false;
}
bool
BaseProxyHandler::isConstructor(JSObject *obj) const
{
return false;
}

View File

@ -248,3 +248,10 @@ DirectProxyHandler::preventExtensions(JSContext *cx, HandleObject proxy) const
RootedObject target(cx, proxy->as<ProxyObject>().target());
return JSObject::preventExtensions(cx, target);
}
bool
DirectProxyHandler::isCallable(JSObject *obj) const
{
JSObject * target = obj->as<ProxyObject>().target();
return target->isCallable();
}

View File

@ -839,18 +839,12 @@ js::proxy_Slice(JSContext *cx, HandleObject proxy, uint32_t begin, uint32_t end,
return Proxy::slice(cx, proxy, begin, end, result);
}
#define PROXY_CLASS(callOp, constructOp) \
PROXY_CLASS_DEF("Proxy", \
0, /* additional slots */ \
JSCLASS_HAS_CACHED_PROTO(JSProto_Proxy), \
callOp, \
constructOp)
const Class js::ProxyObject::class_ =
PROXY_CLASS_DEF("Proxy",
0,
JSCLASS_HAS_CACHED_PROTO(JSProto_Proxy));
const Class js::ProxyObject::uncallableClass_ = PROXY_CLASS(nullptr, nullptr);
const Class js::ProxyObject::callableClass_ = PROXY_CLASS(proxy_Call, proxy_Construct);
const Class* const js::CallableProxyClassPtr = &ProxyObject::callableClass_;
const Class* const js::UncallableProxyClassPtr = &ProxyObject::uncallableClass_;
const Class* const js::ProxyClassPtr = &js::ProxyObject::class_;
JS_FRIEND_API(JSObject *)
js::NewProxyObject(JSContext *cx, const BaseProxyHandler *handler, HandleValue priv, JSObject *proto_,
@ -865,7 +859,8 @@ ProxyObject::renew(JSContext *cx, const BaseProxyHandler *handler, Value priv)
{
JS_ASSERT_IF(IsCrossCompartmentWrapper(this), IsDeadProxyObject(this));
JS_ASSERT(getParent() == cx->global());
JS_ASSERT(getClass() == &uncallableClass_);
JS_ASSERT(getClass() == &ProxyObject::class_);
JS_ASSERT(!isCallable());
JS_ASSERT(!getClass()->ext.innerObject);
JS_ASSERT(getTaggedProto().isLazy());

View File

@ -1083,6 +1083,13 @@ ScriptedDirectProxyHandler::construct(JSContext *cx, HandleObject proxy, const C
return true;
}
bool
ScriptedDirectProxyHandler::isCallable(JSObject *obj) const
{
MOZ_ASSERT(obj->as<ProxyObject>().handler() == &ScriptedDirectProxyHandler::singleton);
return obj->as<ProxyObject>().extra(IS_CALLABLE_EXTRA).toBoolean();
}
const char ScriptedDirectProxyHandler::family = 0;
const ScriptedDirectProxyHandler ScriptedDirectProxyHandler::singleton;
@ -1102,15 +1109,14 @@ js::proxy(JSContext *cx, unsigned argc, jsval *vp)
if (!handler)
return false;
RootedValue priv(cx, ObjectValue(*target));
ProxyOptions options;
options.selectDefaultClass(target->isCallable());
ProxyObject *proxy =
ProxyObject::New(cx, &ScriptedDirectProxyHandler::singleton,
priv, TaggedProto(TaggedProto::LazyProto), cx->global(),
options);
JSObject *proxy =
NewProxyObject(cx, &ScriptedDirectProxyHandler::singleton,
priv, TaggedProto::LazyProto, cx->global());
if (!proxy)
return false;
proxy->setExtra(ScriptedDirectProxyHandler::HANDLER_EXTRA, ObjectValue(*handler));
proxy->as<ProxyObject>().setExtra(ScriptedDirectProxyHandler::HANDLER_EXTRA, ObjectValue(*handler));
proxy->as<ProxyObject>().setExtra(ScriptedDirectProxyHandler::IS_CALLABLE_EXTRA,
BooleanValue(target->isCallable()));
args.rval().setObject(*proxy);
return true;
}

View File

@ -54,6 +54,12 @@ class ScriptedDirectProxyHandler : public DirectProxyHandler {
/* Spidermonkey extensions. */
virtual bool call(JSContext *cx, HandleObject proxy, const CallArgs &args) const MOZ_OVERRIDE;
virtual bool construct(JSContext *cx, HandleObject proxy, const CallArgs &args) const MOZ_OVERRIDE;
virtual bool isCallable(JSObject *obj) const MOZ_OVERRIDE;
virtual bool isConstructor(JSObject *obj) const MOZ_OVERRIDE {
// For now we maintain the broken behavior that a scripted proxy is constructable if it's
// callable. See bug 929467.
return isCallable(obj);
}
virtual bool isScripted() const MOZ_OVERRIDE { return true; }
static const char family;
@ -62,6 +68,7 @@ class ScriptedDirectProxyHandler : public DirectProxyHandler {
// The "proxy extra" slot index in which the handler is stored. Revocable proxies need to set
// this at revocation time.
static const int HANDLER_EXTRA = 0;
static const int IS_CALLABLE_EXTRA = 1;
// The "function extended" slot index in which the revocation object is stored. Per spec, this
// is to be cleared during the first revocation.
static const int REVOKE_SLOT = 0;

View File

@ -328,29 +328,6 @@ ScriptedIndirectProxyHandler::iterate(JSContext *cx, HandleObject proxy, unsigne
ReturnedValueMustNotBePrimitive(cx, proxy, cx->names().iterate, vp);
}
bool
ScriptedIndirectProxyHandler::call(JSContext *cx, HandleObject proxy, const CallArgs &args) const
{
assertEnteredPolicy(cx, proxy, JSID_VOID, CALL);
RootedObject ccHolder(cx, &proxy->as<ProxyObject>().extra(0).toObject());
JS_ASSERT(ccHolder->getClass() == &CallConstructHolder);
RootedValue call(cx, ccHolder->getReservedSlot(0));
JS_ASSERT(call.isObject() && call.toObject().isCallable());
return Invoke(cx, args.thisv(), call, args.length(), args.array(), args.rval());
}
bool
ScriptedIndirectProxyHandler::construct(JSContext *cx, HandleObject proxy, const CallArgs &args) const
{
assertEnteredPolicy(cx, proxy, JSID_VOID, CALL);
RootedObject ccHolder(cx, &proxy->as<ProxyObject>().extra(0).toObject());
JS_ASSERT(ccHolder->getClass() == &CallConstructHolder);
RootedValue construct(cx, ccHolder->getReservedSlot(1));
JS_ASSERT(construct.isObject() && construct.toObject().isCallable());
return InvokeConstructor(cx, construct, args.length(), args.array(),
args.rval().address());
}
bool
ScriptedIndirectProxyHandler::nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
CallArgs args) const
@ -375,6 +352,31 @@ ScriptedIndirectProxyHandler::fun_toString(JSContext *cx, HandleObject proxy, un
const ScriptedIndirectProxyHandler ScriptedIndirectProxyHandler::singleton;
bool
CallableScriptedIndirectProxyHandler::call(JSContext *cx, HandleObject proxy, const CallArgs &args) const
{
assertEnteredPolicy(cx, proxy, JSID_VOID, CALL);
RootedObject ccHolder(cx, &proxy->as<ProxyObject>().extra(0).toObject());
JS_ASSERT(ccHolder->getClass() == &CallConstructHolder);
RootedValue call(cx, ccHolder->getReservedSlot(0));
JS_ASSERT(call.isObject() && call.toObject().isCallable());
return Invoke(cx, args.thisv(), call, args.length(), args.array(), args.rval());
}
bool
CallableScriptedIndirectProxyHandler::construct(JSContext *cx, HandleObject proxy, const CallArgs &args) const
{
assertEnteredPolicy(cx, proxy, JSID_VOID, CALL);
RootedObject ccHolder(cx, &proxy->as<ProxyObject>().extra(0).toObject());
JS_ASSERT(ccHolder->getClass() == &CallConstructHolder);
RootedValue construct(cx, ccHolder->getReservedSlot(1));
JS_ASSERT(construct.isObject() && construct.toObject().isCallable());
return InvokeConstructor(cx, construct, args.length(), args.array(),
args.rval().address());
}
const CallableScriptedIndirectProxyHandler CallableScriptedIndirectProxyHandler::singleton;
bool
js::proxy_create(JSContext *cx, unsigned argc, Value *vp)
{
@ -448,11 +450,9 @@ js::proxy_createFunction(JSContext *cx, unsigned argc, Value *vp)
ccHolder->setReservedSlot(1, ObjectValue(*construct));
RootedValue priv(cx, ObjectValue(*handler));
ProxyOptions options;
options.selectDefaultClass(true);
JSObject *proxy =
ProxyObject::New(cx, &ScriptedIndirectProxyHandler::singleton,
priv, TaggedProto(proto), parent, options);
NewProxyObject(cx, &CallableScriptedIndirectProxyHandler::singleton,
priv, proto, parent);
if (!proxy)
return false;
proxy->as<ProxyObject>().setExtra(0, ObjectValue(*ccHolder));

View File

@ -45,8 +45,6 @@ class ScriptedIndirectProxyHandler : public BaseProxyHandler
/* Spidermonkey extensions. */
virtual bool isExtensible(JSContext *cx, HandleObject proxy, bool *extensible) const MOZ_OVERRIDE;
virtual bool call(JSContext *cx, HandleObject proxy, const CallArgs &args) const MOZ_OVERRIDE;
virtual bool construct(JSContext *cx, HandleObject proxy, const CallArgs &args) const MOZ_OVERRIDE;
virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
CallArgs args) const MOZ_OVERRIDE;
virtual JSString *fun_toString(JSContext *cx, HandleObject proxy, unsigned indent) const MOZ_OVERRIDE;
@ -56,6 +54,24 @@ class ScriptedIndirectProxyHandler : public BaseProxyHandler
static const ScriptedIndirectProxyHandler singleton;
};
/* Derived class to handle Proxy.createFunction() */
class CallableScriptedIndirectProxyHandler : public ScriptedIndirectProxyHandler
{
public:
CallableScriptedIndirectProxyHandler() : ScriptedIndirectProxyHandler() { }
virtual bool call(JSContext *cx, HandleObject proxy, const CallArgs &args) const MOZ_OVERRIDE;
virtual bool construct(JSContext *cx, HandleObject proxy, const CallArgs &args) const MOZ_OVERRIDE;
virtual bool isCallable(JSObject *obj) const MOZ_OVERRIDE {
return true;
}
virtual bool isConstructor(JSObject *obj) const MOZ_OVERRIDE {
return true;
}
static const CallableScriptedIndirectProxyHandler singleton;
};
bool
proxy_create(JSContext *cx, unsigned argc, Value *vp);

View File

@ -34,18 +34,12 @@ Wrapper::defaultValue(JSContext *cx, HandleObject proxy, JSType hint, MutableHan
JSObject *
Wrapper::New(JSContext *cx, JSObject *obj, JSObject *parent, const Wrapper *handler,
const WrapperOptions *options)
const WrapperOptions &options)
{
JS_ASSERT(parent);
RootedValue priv(cx, ObjectValue(*obj));
mozilla::Maybe<WrapperOptions> opts;
if (!options) {
opts.emplace();
opts->selectDefaultClass(obj->isCallable());
options = opts.ptr();
}
return NewProxyObject(cx, handler, priv, options->proto(), parent, *options);
return NewProxyObject(cx, handler, priv, options.proto(), parent, options);
}
JSObject *
@ -70,6 +64,15 @@ Wrapper::wrappedObject(JSObject *wrapper)
return wrapper->as<ProxyObject>().target();
}
bool
Wrapper::isConstructor(JSObject *obj) const
{
// For now, all wrappers are constructable if they are callable. We will want to eventually
// decouple this behavior, but none of the Wrapper infrastructure is currently prepared for
// that.
return isCallable(obj);
}
JS_FRIEND_API(JSObject *)
js::UncheckedUnwrap(JSObject *wrapped, bool stopAtOuter, unsigned *flagsp)
{

View File

@ -4032,9 +4032,8 @@ WrapWithProto(JSContext *cx, unsigned argc, jsval *vp)
WrapperOptions options(cx);
options.setProto(proto.toObjectOrNull());
options.selectDefaultClass(obj.toObject().isCallable());
JSObject *wrapped = Wrapper::New(cx, &obj.toObject(), &obj.toObject().global(),
&Wrapper::singletonWithPrototype, &options);
&Wrapper::singletonWithPrototype, options);
if (!wrapped)
return false;

View File

@ -468,10 +468,11 @@ js::Invoke(JSContext *cx, CallArgs args, MaybeConstruct construct)
if (MOZ_UNLIKELY(clasp == &js_NoSuchMethodClass))
return NoSuchMethod(cx, args.length(), args.base());
#endif
JS_ASSERT_IF(construct, !clasp->construct);
if (!clasp->call)
JS_ASSERT_IF(construct, !callee.constructHook());
JSNative call = callee.callHook();
if (!call)
return ReportIsNotFunction(cx, args.calleev(), args.length() + 1, construct);
return CallJSNative(cx, clasp->call, args);
return CallJSNative(cx, call, args);
}
/* Invoke native functions. */
@ -571,11 +572,11 @@ js::InvokeConstructor(JSContext *cx, CallArgs args)
return true;
}
const Class *clasp = callee.getClass();
if (!clasp->construct)
JSNative construct = callee.constructHook();
if (!construct)
return ReportIsNotFunction(cx, args.calleev(), args.length() + 1, CONSTRUCT);
return CallJSNativeConstructor(cx, clasp->construct, args);
return CallJSNativeConstructor(cx, construct, args);
}
bool

View File

@ -42,8 +42,9 @@ class ProxyObject : public JSObject
return const_cast<ProxyObject*>(this)->private_().toObjectOrNull();
}
const BaseProxyHandler *handler() {
return static_cast<const BaseProxyHandler*>(GetReservedSlot(this, HANDLER_SLOT).toPrivate());
const BaseProxyHandler *handler() const {
return static_cast<const BaseProxyHandler*>(
GetReservedSlot(const_cast<ProxyObject*>(this), HANDLER_SLOT).toPrivate());
}
void initHandler(const BaseProxyHandler *handler);
@ -85,9 +86,13 @@ class ProxyObject : public JSObject
// proxy_Trace is just a trivial wrapper around ProxyObject::trace for
// friend api exposure.
// Proxy classes are not allowed to have call or construct hooks directly. Their
// callability is instead decided by a trap call
return clasp->isProxy() &&
(clasp->flags & JSCLASS_IMPLEMENTS_BARRIERS) &&
clasp->trace == proxy_Trace &&
!clasp->call && !clasp->construct &&
JSCLASS_RESERVED_SLOTS(clasp) >= PROXY_MINIMUM_SLOTS;
}
@ -100,8 +105,7 @@ class ProxyObject : public JSObject
void nuke(const BaseProxyHandler *handler);
static const Class callableClass_;
static const Class uncallableClass_;
static const Class class_;
};
} // namespace js

View File

@ -1657,7 +1657,7 @@ DebugScopeObject::getMaybeSentinelValue(JSContext *cx, HandleId id, MutableHandl
bool
js_IsDebugScopeSlow(ProxyObject *proxy)
{
JS_ASSERT(proxy->hasClass(&ProxyObject::uncallableClass_));
JS_ASSERT(proxy->hasClass(&ProxyObject::class_));
return proxy->handler() == &DebugScopeProxy::singleton;
}

View File

@ -914,7 +914,7 @@ JSObject::is<js::DebugScopeObject>() const
extern bool js_IsDebugScopeSlow(js::ProxyObject *proxy);
// Note: don't use is<ProxyObject>() here -- it also matches subclasses!
return hasClass(&js::ProxyObject::uncallableClass_) &&
return hasClass(&js::ProxyObject::class_) &&
js_IsDebugScopeSlow(&const_cast<JSObject*>(this)->as<js::ProxyObject>());
}

View File

@ -562,11 +562,9 @@ WrapCallable(JSContext *cx, JSObject *callable, JSObject *sandboxProtoProxy)
&xpc::sandboxProxyHandler);
RootedValue priv(cx, ObjectValue(*callable));
js::ProxyOptions options;
options.selectDefaultClass(true);
return js::NewProxyObject(cx, &xpc::sandboxCallableProxyHandler,
priv, nullptr,
sandboxProtoProxy, options);
sandboxProtoProxy);
}
template<typename Op>