gecko-dev/js/src/jsproxy.cpp

3216 lines
103 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "jsproxy.h"
#include <string.h>
#include "jsapi.h"
#include "jscntxt.h"
#include "jsfun.h"
#include "jsgc.h"
#include "jswrapper.h"
#include "gc/Marking.h"
#include "vm/WrapperObject.h"
#include "jsatominlines.h"
#include "jsinferinlines.h"
#include "jsobjinlines.h"
#include "vm/ObjectImpl-inl.h"
using namespace js;
using namespace js::gc;
using mozilla::ArrayLength;
void
js::AutoEnterPolicy::reportErrorIfExceptionIsNotPending(JSContext *cx, jsid id)
{
if (JS_IsExceptionPending(cx))
return;
if (JSID_IS_VOID(id)) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
JSMSG_OBJECT_ACCESS_DENIED);
} else {
JSString *str = IdToString(cx, id);
const jschar *prop = str ? str->getCharsZ(cx) : nullptr;
JS_ReportErrorNumberUC(cx, js_GetErrorMessage, nullptr,
JSMSG_PROPERTY_ACCESS_DENIED, prop);
}
}
#ifdef DEBUG
void
js::AutoEnterPolicy::recordEnter(JSContext *cx, HandleObject proxy, HandleId id, Action act)
{
if (allowed()) {
context = cx;
enteredProxy.construct(proxy);
enteredId.construct(id);
enteredAction = act;
prev = cx->runtime()->enteredPolicy;
cx->runtime()->enteredPolicy = this;
}
}
void
js::AutoEnterPolicy::recordLeave()
{
if (!enteredProxy.empty()) {
JS_ASSERT(context->runtime()->enteredPolicy == this);
context->runtime()->enteredPolicy = prev;
}
}
JS_FRIEND_API(void)
js::assertEnteredPolicy(JSContext *cx, JSObject *proxy, jsid id,
BaseProxyHandler::Action act)
{
MOZ_ASSERT(proxy->is<ProxyObject>());
MOZ_ASSERT(cx->runtime()->enteredPolicy);
MOZ_ASSERT(cx->runtime()->enteredPolicy->enteredProxy.ref().get() == proxy);
MOZ_ASSERT(cx->runtime()->enteredPolicy->enteredId.ref().get() == id);
MOZ_ASSERT(cx->runtime()->enteredPolicy->enteredAction & act);
}
#endif
BaseProxyHandler::BaseProxyHandler(const void *family)
: mFamily(family),
mHasPrototype(false),
mHasSecurityPolicy(false)
{
}
BaseProxyHandler::~BaseProxyHandler()
{
}
bool
BaseProxyHandler::enter(JSContext *cx, HandleObject wrapper, HandleId id, Action act,
bool *bp)
{
*bp = true;
return true;
}
bool
BaseProxyHandler::has(JSContext *cx, HandleObject proxy, HandleId id, bool *bp)
{
assertEnteredPolicy(cx, proxy, id, GET);
Rooted<PropertyDescriptor> desc(cx);
if (!getPropertyDescriptor(cx, proxy, id, &desc))
return false;
*bp = !!desc.object();
return true;
}
bool
BaseProxyHandler::hasOwn(JSContext *cx, HandleObject proxy, HandleId id, bool *bp)
{
// Note: Proxy::set needs to invoke hasOwn to determine where the setter
// lives, so we allow SET operations to invoke us.
assertEnteredPolicy(cx, proxy, id, GET | SET);
Rooted<PropertyDescriptor> desc(cx);
if (!getOwnPropertyDescriptor(cx, proxy, id, &desc))
return false;
*bp = !!desc.object();
return true;
}
bool
BaseProxyHandler::get(JSContext *cx, HandleObject proxy, HandleObject receiver,
HandleId id, MutableHandleValue vp)
{
assertEnteredPolicy(cx, proxy, id, GET);
Rooted<PropertyDescriptor> desc(cx);
if (!getPropertyDescriptor(cx, proxy, id, &desc))
return false;
if (!desc.object()) {
vp.setUndefined();
return true;
}
if (!desc.getter() ||
(!desc.hasGetterObject() && desc.getter() == JS_PropertyStub))
{
vp.set(desc.value());
return true;
}
if (desc.hasGetterObject())
return InvokeGetterOrSetter(cx, receiver, ObjectValue(*desc.getterObject()),
0, nullptr, vp);
if (!desc.isShared())
vp.set(desc.value());
else
vp.setUndefined();
return CallJSPropertyOp(cx, desc.getter(), receiver, id, vp);
}
bool
BaseProxyHandler::set(JSContext *cx, HandleObject proxy, HandleObject receiver,
HandleId id, bool strict, MutableHandleValue vp)
{
assertEnteredPolicy(cx, proxy, id, SET);
Rooted<PropertyDescriptor> desc(cx);
if (!getOwnPropertyDescriptor(cx, proxy, id, &desc))
return false;
/* The control-flow here differs from ::get() because of the fall-through case below. */
if (desc.object()) {
// Check for read-only properties.
if (desc.isReadonly())
return strict ? Throw(cx, id, JSMSG_CANT_REDEFINE_PROP) : true;
if (!desc.setter()) {
// Be wary of the odd explicit undefined setter case possible through
// Object.defineProperty.
if (!desc.hasSetterObject())
desc.setSetter(JS_StrictPropertyStub);
} else if (desc.hasSetterObject() || desc.setter() != JS_StrictPropertyStub) {
if (!CallSetter(cx, receiver, id, desc.setter(), desc.attributes(), strict, vp))
return false;
if (!proxy->is<ProxyObject>() || proxy->as<ProxyObject>().handler() != this)
return true;
if (desc.isShared())
return true;
}
if (!desc.getter()) {
// Same as above for the null setter case.
if (!desc.hasGetterObject())
desc.setGetter(JS_PropertyStub);
}
desc.value().set(vp.get());
return defineProperty(cx, receiver, id, &desc);
}
if (!getPropertyDescriptor(cx, proxy, id, &desc))
return false;
if (desc.object()) {
// Check for read-only properties.
if (desc.isReadonly())
return strict ? Throw(cx, id, JSMSG_CANT_REDEFINE_PROP) : true;
if (!desc.setter()) {
// Be wary of the odd explicit undefined setter case possible through
// Object.defineProperty.
if (!desc.hasSetterObject())
desc.setSetter(JS_StrictPropertyStub);
} else if (desc.hasSetterObject() || desc.setter() != JS_StrictPropertyStub) {
if (!CallSetter(cx, receiver, id, desc.setter(), desc.attributes(), strict, vp))
return false;
if (!proxy->is<ProxyObject>() || proxy->as<ProxyObject>().handler() != this)
return true;
if (desc.isShared())
return true;
}
if (!desc.getter()) {
// Same as above for the null setter case.
if (!desc.hasGetterObject())
desc.setGetter(JS_PropertyStub);
}
desc.value().set(vp.get());
return defineProperty(cx, receiver, id, &desc);
}
desc.object().set(receiver);
desc.value().set(vp.get());
desc.setAttributes(JSPROP_ENUMERATE);
desc.setGetter(nullptr);
desc.setSetter(nullptr); // Pick up the class getter/setter.
return defineProperty(cx, receiver, id, &desc);
}
bool
BaseProxyHandler::keys(JSContext *cx, HandleObject proxy, AutoIdVector &props)
{
assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE);
JS_ASSERT(props.length() == 0);
if (!getOwnPropertyNames(cx, proxy, props))
return false;
/* Select only the enumerable properties through in-place iteration. */
Rooted<PropertyDescriptor> desc(cx);
RootedId id(cx);
size_t i = 0;
for (size_t j = 0, len = props.length(); j < len; j++) {
JS_ASSERT(i <= j);
id = props[j];
AutoWaivePolicy policy(cx, proxy, id, BaseProxyHandler::GET);
if (!getOwnPropertyDescriptor(cx, proxy, id, &desc))
return false;
if (desc.object() && desc.isEnumerable())
props[i++].set(id);
}
JS_ASSERT(i <= props.length());
props.resize(i);
return true;
}
bool
BaseProxyHandler::iterate(JSContext *cx, HandleObject proxy, unsigned flags, MutableHandleValue vp)
{
assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE);
AutoIdVector props(cx);
if ((flags & JSITER_OWNONLY)
? !keys(cx, proxy, props)
: !enumerate(cx, proxy, props)) {
return false;
}
return EnumeratedIdVectorToIterator(cx, proxy, flags, props, vp);
}
bool
BaseProxyHandler::call(JSContext *cx, HandleObject proxy, const CallArgs &args)
{
MOZ_ASSUME_UNREACHABLE("callable proxies should implement call trap");
}
bool
BaseProxyHandler::construct(JSContext *cx, HandleObject proxy, const CallArgs &args)
{
MOZ_ASSUME_UNREACHABLE("callable proxies should implement construct trap");
}
const char *
BaseProxyHandler::className(JSContext *cx, HandleObject proxy)
{
return proxy->isCallable() ? "Function" : "Object";
}
JSString *
BaseProxyHandler::fun_toString(JSContext *cx, HandleObject proxy, unsigned indent)
{
if (proxy->isCallable())
return JS_NewStringCopyZ(cx, "function () {\n [native code]\n}");
RootedValue v(cx, ObjectValue(*proxy));
ReportIsNotFunction(cx, v);
return nullptr;
}
bool
BaseProxyHandler::regexp_toShared(JSContext *cx, HandleObject proxy,
RegExpGuard *g)
{
MOZ_ASSUME_UNREACHABLE("This should have been a wrapped regexp");
}
bool
BaseProxyHandler::defaultValue(JSContext *cx, HandleObject proxy, JSType hint,
MutableHandleValue vp)
{
return DefaultValue(cx, proxy, hint, vp);
}
bool
BaseProxyHandler::nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl, CallArgs args)
{
ReportIncompatible(cx, args);
return false;
}
bool
BaseProxyHandler::hasInstance(JSContext *cx, HandleObject proxy, MutableHandleValue v, bool *bp)
{
assertEnteredPolicy(cx, proxy, JSID_VOID, GET);
RootedValue val(cx, ObjectValue(*proxy.get()));
js_ReportValueError(cx, JSMSG_BAD_INSTANCEOF_RHS,
JSDVG_SEARCH_STACK, val, js::NullPtr());
return false;
}
bool
BaseProxyHandler::objectClassIs(HandleObject proxy, ESClassValue classValue, JSContext *cx)
{
return false;
}
void
BaseProxyHandler::finalize(JSFreeOp *fop, JSObject *proxy)
{
}
JSObject *
BaseProxyHandler::weakmapKeyDelegate(JSObject *proxy)
{
return nullptr;
}
bool
BaseProxyHandler::getPrototypeOf(JSContext *cx, HandleObject proxy, MutableHandleObject protop)
{
MOZ_ASSUME_UNREACHABLE("Must override getPrototypeOf with lazy prototype.");
}
bool
BaseProxyHandler::setPrototypeOf(JSContext *cx, HandleObject, HandleObject, bool *)
{
// Disallow sets of protos on proxies with lazy protos, but no hook.
// This keeps us away from the footgun of having the first proto set opt
// you out of having dynamic protos altogether.
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_SETPROTOTYPEOF_FAIL,
"incompatible Proxy");
return false;
}
bool
BaseProxyHandler::watch(JSContext *cx, HandleObject proxy, HandleId id, HandleObject callable)
{
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_WATCH,
proxy->getClass()->name);
return false;
}
bool
BaseProxyHandler::unwatch(JSContext *cx, HandleObject proxy, HandleId id)
{
return true;
}
bool
BaseProxyHandler::slice(JSContext *cx, HandleObject proxy, uint32_t begin, uint32_t end,
HandleObject result)
{
assertEnteredPolicy(cx, proxy, JSID_VOID, GET);
return js::SliceSlowly(cx, proxy, proxy, begin, end, result);
}
bool
DirectProxyHandler::getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
MutableHandle<PropertyDescriptor> desc)
{
assertEnteredPolicy(cx, proxy, id, GET | SET);
JS_ASSERT(!hasPrototype()); // Should never be called if there's a prototype.
RootedObject target(cx, proxy->as<ProxyObject>().target());
return JS_GetPropertyDescriptorById(cx, target, id, desc);
}
bool
DirectProxyHandler::getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
MutableHandle<PropertyDescriptor> desc)
{
assertEnteredPolicy(cx, proxy, id, GET | SET);
RootedObject target(cx, proxy->as<ProxyObject>().target());
return js::GetOwnPropertyDescriptor(cx, target, id, desc);
}
bool
DirectProxyHandler::defineProperty(JSContext *cx, HandleObject proxy, HandleId id,
MutableHandle<PropertyDescriptor> desc)
{
assertEnteredPolicy(cx, proxy, id, SET);
RootedObject target(cx, proxy->as<ProxyObject>().target());
RootedValue v(cx, desc.value());
return CheckDefineProperty(cx, target, id, v, desc.attributes(), desc.getter(), desc.setter()) &&
JS_DefinePropertyById(cx, target, id, v, desc.attributes(), desc.getter(), desc.setter());
}
bool
DirectProxyHandler::getOwnPropertyNames(JSContext *cx, HandleObject proxy,
AutoIdVector &props)
{
assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE);
RootedObject target(cx, proxy->as<ProxyObject>().target());
return GetPropertyNames(cx, target, JSITER_OWNONLY | JSITER_HIDDEN, &props);
}
bool
DirectProxyHandler::delete_(JSContext *cx, HandleObject proxy, HandleId id, bool *bp)
{
assertEnteredPolicy(cx, proxy, id, SET);
RootedObject target(cx, proxy->as<ProxyObject>().target());
return JSObject::deleteGeneric(cx, target, id, bp);
}
bool
DirectProxyHandler::enumerate(JSContext *cx, HandleObject proxy,
AutoIdVector &props)
{
assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE);
JS_ASSERT(!hasPrototype()); // Should never be called if there's a prototype.
RootedObject target(cx, proxy->as<ProxyObject>().target());
return GetPropertyNames(cx, target, 0, &props);
}
bool
DirectProxyHandler::call(JSContext *cx, HandleObject proxy, const CallArgs &args)
{
assertEnteredPolicy(cx, proxy, JSID_VOID, CALL);
RootedValue target(cx, proxy->as<ProxyObject>().private_());
return Invoke(cx, args.thisv(), target, args.length(), args.array(), args.rval());
}
bool
DirectProxyHandler::construct(JSContext *cx, HandleObject proxy, const CallArgs &args)
{
assertEnteredPolicy(cx, proxy, JSID_VOID, CALL);
RootedValue target(cx, proxy->as<ProxyObject>().private_());
return InvokeConstructor(cx, target, args.length(), args.array(), args.rval().address());
}
bool
DirectProxyHandler::nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
CallArgs args)
{
args.setThis(ObjectValue(*args.thisv().toObject().as<ProxyObject>().target()));
if (!test(args.thisv())) {
ReportIncompatible(cx, args);
return false;
}
return CallNativeImpl(cx, impl, args);
}
bool
DirectProxyHandler::hasInstance(JSContext *cx, HandleObject proxy, MutableHandleValue v,
bool *bp)
{
assertEnteredPolicy(cx, proxy, JSID_VOID, GET);
bool b;
RootedObject target(cx, proxy->as<ProxyObject>().target());
if (!HasInstance(cx, target, v, &b))
return false;
*bp = !!b;
return true;
}
bool
DirectProxyHandler::getPrototypeOf(JSContext *cx, HandleObject proxy, MutableHandleObject protop)
{
RootedObject target(cx, proxy->as<ProxyObject>().target());
return JSObject::getProto(cx, target, protop);
}
bool
DirectProxyHandler::setPrototypeOf(JSContext *cx, HandleObject proxy, HandleObject proto, bool *bp)
{
RootedObject target(cx, proxy->as<ProxyObject>().target());
return JSObject::setProto(cx, target, proto, bp);
}
bool
DirectProxyHandler::objectClassIs(HandleObject proxy, ESClassValue classValue,
JSContext *cx)
{
RootedObject target(cx, proxy->as<ProxyObject>().target());
return ObjectClassIs(target, classValue, cx);
}
const char *
DirectProxyHandler::className(JSContext *cx, HandleObject proxy)
{
assertEnteredPolicy(cx, proxy, JSID_VOID, GET);
RootedObject target(cx, proxy->as<ProxyObject>().target());
return JSObject::className(cx, target);
}
JSString *
DirectProxyHandler::fun_toString(JSContext *cx, HandleObject proxy,
unsigned indent)
{
assertEnteredPolicy(cx, proxy, JSID_VOID, GET);
RootedObject target(cx, proxy->as<ProxyObject>().target());
return fun_toStringHelper(cx, target, indent);
}
bool
DirectProxyHandler::regexp_toShared(JSContext *cx, HandleObject proxy,
RegExpGuard *g)
{
RootedObject target(cx, proxy->as<ProxyObject>().target());
return RegExpToShared(cx, target, g);
}
JSObject *
DirectProxyHandler::weakmapKeyDelegate(JSObject *proxy)
{
return UncheckedUnwrap(proxy);
}
DirectProxyHandler::DirectProxyHandler(const void *family)
: BaseProxyHandler(family)
{
}
bool
DirectProxyHandler::has(JSContext *cx, HandleObject proxy, HandleId id, bool *bp)
{
assertEnteredPolicy(cx, proxy, id, GET);
JS_ASSERT(!hasPrototype()); // Should never be called if there's a prototype.
bool found;
RootedObject target(cx, proxy->as<ProxyObject>().target());
if (!JS_HasPropertyById(cx, target, id, &found))
return false;
*bp = !!found;
return true;
}
bool
DirectProxyHandler::hasOwn(JSContext *cx, HandleObject proxy, HandleId id, bool *bp)
{
// Note: Proxy::set needs to invoke hasOwn to determine where the setter
// lives, so we allow SET operations to invoke us.
assertEnteredPolicy(cx, proxy, id, GET | SET);
RootedObject target(cx, proxy->as<ProxyObject>().target());
Rooted<PropertyDescriptor> desc(cx);
if (!JS_GetPropertyDescriptorById(cx, target, id, &desc))
return false;
*bp = (desc.object() == target);
return true;
}
bool
DirectProxyHandler::get(JSContext *cx, HandleObject proxy, HandleObject receiver,
HandleId id, MutableHandleValue vp)
{
assertEnteredPolicy(cx, proxy, id, GET);
RootedObject target(cx, proxy->as<ProxyObject>().target());
return JSObject::getGeneric(cx, target, receiver, id, vp);
}
bool
DirectProxyHandler::set(JSContext *cx, HandleObject proxy, HandleObject receiver,
HandleId id, bool strict, MutableHandleValue vp)
{
assertEnteredPolicy(cx, proxy, id, SET);
RootedObject target(cx, proxy->as<ProxyObject>().target());
return JSObject::setGeneric(cx, target, receiver, id, vp, strict);
}
bool
DirectProxyHandler::keys(JSContext *cx, HandleObject proxy, AutoIdVector &props)
{
assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE);
RootedObject target(cx, proxy->as<ProxyObject>().target());
return GetPropertyNames(cx, target, JSITER_OWNONLY, &props);
}
bool
DirectProxyHandler::iterate(JSContext *cx, HandleObject proxy, unsigned flags,
MutableHandleValue vp)
{
assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE);
JS_ASSERT(!hasPrototype()); // Should never be called if there's a prototype.
RootedObject target(cx, proxy->as<ProxyObject>().target());
return GetIterator(cx, target, flags, vp);
}
bool
DirectProxyHandler::isExtensible(JSContext *cx, HandleObject proxy, bool *extensible)
{
RootedObject target(cx, proxy->as<ProxyObject>().target());
return JSObject::isExtensible(cx, target, extensible);
}
bool
DirectProxyHandler::preventExtensions(JSContext *cx, HandleObject proxy)
{
RootedObject target(cx, proxy->as<ProxyObject>().target());
return JSObject::preventExtensions(cx, target);
}
static bool
GetFundamentalTrap(JSContext *cx, HandleObject handler, HandlePropertyName name,
MutableHandleValue fvalp)
{
JS_CHECK_RECURSION(cx, return false);
return JSObject::getProperty(cx, handler, handler, name, fvalp);
}
static bool
GetDerivedTrap(JSContext *cx, HandleObject handler, HandlePropertyName name,
MutableHandleValue fvalp)
{
JS_ASSERT(name == cx->names().has ||
name == cx->names().hasOwn ||
name == cx->names().get ||
name == cx->names().set ||
name == cx->names().keys ||
name == cx->names().iterate);
return JSObject::getProperty(cx, handler, handler, name, fvalp);
}
static bool
Trap(JSContext *cx, HandleObject handler, HandleValue fval, unsigned argc, Value* argv,
MutableHandleValue rval)
{
return Invoke(cx, ObjectValue(*handler), fval, argc, argv, rval);
}
static bool
Trap1(JSContext *cx, HandleObject handler, HandleValue fval, HandleId id, MutableHandleValue rval)
{
rval.set(IdToValue(id)); // Re-use out-param to avoid Rooted overhead.
JSString *str = ToString<CanGC>(cx, rval);
if (!str)
return false;
rval.setString(str);
return Trap(cx, handler, fval, 1, rval.address(), rval);
}
static bool
Trap2(JSContext *cx, HandleObject handler, HandleValue fval, HandleId id, Value v_,
MutableHandleValue rval)
{
RootedValue v(cx, v_);
rval.set(IdToValue(id)); // Re-use out-param to avoid Rooted overhead.
JSString *str = ToString<CanGC>(cx, rval);
if (!str)
return false;
rval.setString(str);
JS::AutoValueArray<2> argv(cx);
argv[0].set(rval);
argv[1].set(v);
return Trap(cx, handler, fval, 2, argv.begin(), rval);
}
static bool
ParsePropertyDescriptorObject(JSContext *cx, HandleObject obj, const Value &v,
MutableHandle<PropertyDescriptor> desc, bool complete = false)
{
Rooted<PropDesc> d(cx);
if (!d.initialize(cx, v))
return false;
if (complete)
d.complete();
d.populatePropertyDescriptor(obj, desc);
return true;
}
static bool
IndicatePropertyNotFound(MutableHandle<PropertyDescriptor> desc)
{
desc.object().set(nullptr);
return true;
}
static bool
ValueToBool(HandleValue v, bool *bp)
{
*bp = ToBoolean(v);
return true;
}
static bool
ArrayToIdVector(JSContext *cx, const Value &array, AutoIdVector &props)
{
JS_ASSERT(props.length() == 0);
if (array.isPrimitive())
return true;
RootedObject obj(cx, &array.toObject());
uint32_t length;
if (!GetLengthProperty(cx, obj, &length))
return false;
RootedValue v(cx);
for (uint32_t n = 0; n < length; ++n) {
if (!CheckForInterrupt(cx))
return false;
if (!JSObject::getElement(cx, obj, obj, n, &v))
return false;
RootedId id(cx);
if (!ValueToId<CanGC>(cx, v, &id))
return false;
if (!props.append(id))
return false;
}
return true;
}
namespace {
/* Derived class for all scripted indirect proxy handlers. */
class ScriptedIndirectProxyHandler : public BaseProxyHandler
{
public:
ScriptedIndirectProxyHandler();
virtual ~ScriptedIndirectProxyHandler();
/* ES5 Harmony fundamental proxy traps. */
virtual bool preventExtensions(JSContext *cx, HandleObject proxy) MOZ_OVERRIDE;
virtual bool getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
MutableHandle<PropertyDescriptor> desc) MOZ_OVERRIDE;
virtual bool getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
MutableHandle<PropertyDescriptor> desc) MOZ_OVERRIDE;
virtual bool defineProperty(JSContext *cx, HandleObject proxy, HandleId id,
MutableHandle<PropertyDescriptor> desc) MOZ_OVERRIDE;
virtual bool getOwnPropertyNames(JSContext *cx, HandleObject proxy, AutoIdVector &props);
virtual bool delete_(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) MOZ_OVERRIDE;
virtual bool enumerate(JSContext *cx, HandleObject proxy, AutoIdVector &props) MOZ_OVERRIDE;
/* ES5 Harmony derived proxy traps. */
virtual bool has(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) MOZ_OVERRIDE;
virtual bool hasOwn(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) MOZ_OVERRIDE;
virtual bool get(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id,
MutableHandleValue vp) MOZ_OVERRIDE;
virtual bool set(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id,
bool strict, MutableHandleValue vp) MOZ_OVERRIDE;
virtual bool keys(JSContext *cx, HandleObject proxy, AutoIdVector &props) MOZ_OVERRIDE;
virtual bool iterate(JSContext *cx, HandleObject proxy, unsigned flags,
MutableHandleValue vp) MOZ_OVERRIDE;
/* Spidermonkey extensions. */
virtual bool isExtensible(JSContext *cx, HandleObject proxy, bool *extensible) MOZ_OVERRIDE;
virtual bool call(JSContext *cx, HandleObject proxy, const CallArgs &args) MOZ_OVERRIDE;
virtual bool construct(JSContext *cx, HandleObject proxy, const CallArgs &args) MOZ_OVERRIDE;
virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
CallArgs args) MOZ_OVERRIDE;
virtual JSString *fun_toString(JSContext *cx, HandleObject proxy, unsigned indent) MOZ_OVERRIDE;
virtual bool isScripted() MOZ_OVERRIDE { return true; }
static ScriptedIndirectProxyHandler singleton;
};
/*
* Old-style indirect proxies allow callers to specify distinct scripted
* [[Call]] and [[Construct]] traps. We use an intermediate object so that we
* can stash this information in a single reserved slot on the proxy object.
*
* Note - Currently this is slightly unnecesary, because we actually have 2
* extra slots, neither of which are used for ScriptedIndirectProxy. But we're
* eventually moving towards eliminating one of those slots, and so we don't
* want to add a dependency here.
*/
static Class CallConstructHolder = {
"CallConstructHolder",
JSCLASS_HAS_RESERVED_SLOTS(2) | JSCLASS_IS_ANONYMOUS
};
} /* anonymous namespace */
// This variable exists solely to provide a unique address for use as an identifier.
static const char sScriptedIndirectProxyHandlerFamily = 0;
ScriptedIndirectProxyHandler::ScriptedIndirectProxyHandler()
: BaseProxyHandler(&sScriptedIndirectProxyHandlerFamily)
{
}
ScriptedIndirectProxyHandler::~ScriptedIndirectProxyHandler()
{
}
bool
ScriptedIndirectProxyHandler::isExtensible(JSContext *cx, HandleObject proxy, bool *extensible)
{
// Scripted indirect proxies don't support extensibility changes.
*extensible = true;
return true;
}
bool
ScriptedIndirectProxyHandler::preventExtensions(JSContext *cx, HandleObject proxy)
{
// See above.
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_CHANGE_EXTENSIBILITY);
return false;
}
static bool
ReturnedValueMustNotBePrimitive(JSContext *cx, HandleObject proxy, JSAtom *atom, const Value &v)
{
if (v.isPrimitive()) {
JSAutoByteString bytes;
if (AtomToPrintableString(cx, atom, &bytes)) {
RootedValue val(cx, ObjectOrNullValue(proxy));
js_ReportValueError2(cx, JSMSG_BAD_TRAP_RETURN_VALUE,
JSDVG_SEARCH_STACK, val, js::NullPtr(), bytes.ptr());
}
return false;
}
return true;
}
static JSObject *
GetIndirectProxyHandlerObject(JSObject *proxy)
{
return proxy->as<ProxyObject>().private_().toObjectOrNull();
}
bool
ScriptedIndirectProxyHandler::getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
MutableHandle<PropertyDescriptor> desc)
{
RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy));
RootedValue fval(cx), value(cx);
return GetFundamentalTrap(cx, handler, cx->names().getPropertyDescriptor, &fval) &&
Trap1(cx, handler, fval, id, &value) &&
((value.get().isUndefined() && IndicatePropertyNotFound(desc)) ||
(ReturnedValueMustNotBePrimitive(cx, proxy, cx->names().getPropertyDescriptor, value) &&
ParsePropertyDescriptorObject(cx, proxy, value, desc)));
}
bool
ScriptedIndirectProxyHandler::getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
MutableHandle<PropertyDescriptor> desc)
{
RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy));
RootedValue fval(cx), value(cx);
return GetFundamentalTrap(cx, handler, cx->names().getOwnPropertyDescriptor, &fval) &&
Trap1(cx, handler, fval, id, &value) &&
((value.get().isUndefined() && IndicatePropertyNotFound(desc)) ||
(ReturnedValueMustNotBePrimitive(cx, proxy, cx->names().getPropertyDescriptor, value) &&
ParsePropertyDescriptorObject(cx, proxy, value, desc)));
}
bool
ScriptedIndirectProxyHandler::defineProperty(JSContext *cx, HandleObject proxy, HandleId id,
MutableHandle<PropertyDescriptor> desc)
{
RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy));
RootedValue fval(cx), value(cx);
return GetFundamentalTrap(cx, handler, cx->names().defineProperty, &fval) &&
NewPropertyDescriptorObject(cx, desc, &value) &&
Trap2(cx, handler, fval, id, value, &value);
}
bool
ScriptedIndirectProxyHandler::getOwnPropertyNames(JSContext *cx, HandleObject proxy,
AutoIdVector &props)
{
RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy));
RootedValue fval(cx), value(cx);
return GetFundamentalTrap(cx, handler, cx->names().getOwnPropertyNames, &fval) &&
Trap(cx, handler, fval, 0, nullptr, &value) &&
ArrayToIdVector(cx, value, props);
}
bool
ScriptedIndirectProxyHandler::delete_(JSContext *cx, HandleObject proxy, HandleId id, bool *bp)
{
RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy));
RootedValue fval(cx), value(cx);
return GetFundamentalTrap(cx, handler, cx->names().delete_, &fval) &&
Trap1(cx, handler, fval, id, &value) &&
ValueToBool(value, bp);
}
bool
ScriptedIndirectProxyHandler::enumerate(JSContext *cx, HandleObject proxy, AutoIdVector &props)
{
RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy));
RootedValue fval(cx), value(cx);
return GetFundamentalTrap(cx, handler, cx->names().enumerate, &fval) &&
Trap(cx, handler, fval, 0, nullptr, &value) &&
ArrayToIdVector(cx, value, props);
}
bool
ScriptedIndirectProxyHandler::has(JSContext *cx, HandleObject proxy, HandleId id, bool *bp)
{
RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy));
RootedValue fval(cx), value(cx);
if (!GetDerivedTrap(cx, handler, cx->names().has, &fval))
return false;
if (!js_IsCallable(fval))
return BaseProxyHandler::has(cx, proxy, id, bp);
return Trap1(cx, handler, fval, id, &value) &&
ValueToBool(value, bp);
}
bool
ScriptedIndirectProxyHandler::hasOwn(JSContext *cx, HandleObject proxy, HandleId id, bool *bp)
{
RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy));
RootedValue fval(cx), value(cx);
if (!GetDerivedTrap(cx, handler, cx->names().hasOwn, &fval))
return false;
if (!js_IsCallable(fval))
return BaseProxyHandler::hasOwn(cx, proxy, id, bp);
return Trap1(cx, handler, fval, id, &value) &&
ValueToBool(value, bp);
}
bool
ScriptedIndirectProxyHandler::get(JSContext *cx, HandleObject proxy, HandleObject receiver,
HandleId id, MutableHandleValue vp)
{
RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy));
RootedValue idv(cx, IdToValue(id));
JSString *str = ToString<CanGC>(cx, idv);
if (!str)
return false;
RootedValue value(cx, StringValue(str));
JS::AutoValueArray<2> argv(cx);
argv[0].setObjectOrNull(receiver);
argv[1].set(value);
RootedValue fval(cx);
if (!GetDerivedTrap(cx, handler, cx->names().get, &fval))
return false;
if (!js_IsCallable(fval))
return BaseProxyHandler::get(cx, proxy, receiver, id, vp);
return Trap(cx, handler, fval, 2, argv.begin(), vp);
}
bool
ScriptedIndirectProxyHandler::set(JSContext *cx, HandleObject proxy, HandleObject receiver,
HandleId id, bool strict, MutableHandleValue vp)
{
RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy));
RootedValue idv(cx, IdToValue(id));
JSString *str = ToString<CanGC>(cx, idv);
if (!str)
return false;
RootedValue value(cx, StringValue(str));
JS::AutoValueArray<3> argv(cx);
argv[0].setObjectOrNull(receiver);
argv[1].set(value);
argv[2].set(vp);
RootedValue fval(cx);
if (!GetDerivedTrap(cx, handler, cx->names().set, &fval))
return false;
if (!js_IsCallable(fval))
return BaseProxyHandler::set(cx, proxy, receiver, id, strict, vp);
return Trap(cx, handler, fval, 3, argv.begin(), &value);
}
bool
ScriptedIndirectProxyHandler::keys(JSContext *cx, HandleObject proxy, AutoIdVector &props)
{
RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy));
RootedValue value(cx);
if (!GetDerivedTrap(cx, handler, cx->names().keys, &value))
return false;
if (!js_IsCallable(value))
return BaseProxyHandler::keys(cx, proxy, props);
return Trap(cx, handler, value, 0, nullptr, &value) &&
ArrayToIdVector(cx, value, props);
}
bool
ScriptedIndirectProxyHandler::iterate(JSContext *cx, HandleObject proxy, unsigned flags,
MutableHandleValue vp)
{
RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy));
RootedValue value(cx);
if (!GetDerivedTrap(cx, handler, cx->names().iterate, &value))
return false;
if (!js_IsCallable(value))
return BaseProxyHandler::iterate(cx, proxy, flags, vp);
return Trap(cx, handler, value, 0, nullptr, vp) &&
ReturnedValueMustNotBePrimitive(cx, proxy, cx->names().iterate, vp);
}
bool
ScriptedIndirectProxyHandler::call(JSContext *cx, HandleObject proxy, const CallArgs &args)
{
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)
{
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)
{
return BaseProxyHandler::nativeCall(cx, test, impl, args);
}
JSString *
ScriptedIndirectProxyHandler::fun_toString(JSContext *cx, HandleObject proxy, unsigned indent)
{
assertEnteredPolicy(cx, proxy, JSID_VOID, GET);
if (!proxy->isCallable()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
JSMSG_INCOMPATIBLE_PROTO,
js_Function_str, js_toString_str,
"object");
return nullptr;
}
RootedObject obj(cx, &proxy->as<ProxyObject>().extra(0).toObject().getReservedSlot(0).toObject());
return fun_toStringHelper(cx, obj, indent);
}
ScriptedIndirectProxyHandler ScriptedIndirectProxyHandler::singleton;
/* Derived class for all scripted direct proxy handlers. */
class ScriptedDirectProxyHandler : public DirectProxyHandler {
public:
ScriptedDirectProxyHandler();
virtual ~ScriptedDirectProxyHandler();
/* ES5 Harmony fundamental proxy traps. */
virtual bool preventExtensions(JSContext *cx, HandleObject proxy) MOZ_OVERRIDE;
virtual bool getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
MutableHandle<PropertyDescriptor> desc) MOZ_OVERRIDE;
virtual bool getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
MutableHandle<PropertyDescriptor> desc) MOZ_OVERRIDE;
virtual bool defineProperty(JSContext *cx, HandleObject proxy, HandleId id,
MutableHandle<PropertyDescriptor> desc) MOZ_OVERRIDE;
virtual bool getOwnPropertyNames(JSContext *cx, HandleObject proxy, AutoIdVector &props);
virtual bool delete_(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) MOZ_OVERRIDE;
virtual bool enumerate(JSContext *cx, HandleObject proxy, AutoIdVector &props) MOZ_OVERRIDE;
/* ES5 Harmony derived proxy traps. */
virtual bool has(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) MOZ_OVERRIDE;
virtual bool hasOwn(JSContext *cx,HandleObject proxy, HandleId id, bool *bp) MOZ_OVERRIDE;
virtual bool get(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id,
MutableHandleValue vp) MOZ_OVERRIDE;
virtual bool set(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id,
bool strict, MutableHandleValue vp) MOZ_OVERRIDE;
virtual bool keys(JSContext *cx, HandleObject proxy, AutoIdVector &props) MOZ_OVERRIDE;
virtual bool iterate(JSContext *cx, HandleObject proxy, unsigned flags,
MutableHandleValue vp) MOZ_OVERRIDE;
/* ES6 Harmony traps */
virtual bool isExtensible(JSContext *cx, HandleObject proxy, bool *extensible) MOZ_OVERRIDE;
/* Spidermonkey extensions. */
virtual bool call(JSContext *cx, HandleObject proxy, const CallArgs &args) MOZ_OVERRIDE;
virtual bool construct(JSContext *cx, HandleObject proxy, const CallArgs &args) MOZ_OVERRIDE;
virtual bool isScripted() MOZ_OVERRIDE { return true; }
static ScriptedDirectProxyHandler singleton;
};
// This variable exists solely to provide a unique address for use as an identifier.
static const char sScriptedDirectProxyHandlerFamily = 0;
// Aux.2 FromGenericPropertyDescriptor(Desc)
static bool
FromGenericPropertyDescriptor(JSContext *cx, MutableHandle<PropDesc> desc, MutableHandleValue rval)
{
// Aux.2 step 1
if (desc.isUndefined()) {
rval.setUndefined();
return true;
}
// steps 3-9
if (!desc.makeObject(cx))
return false;
rval.set(desc.descriptorValue());
return true;
}
/*
* Aux.3 NormalizePropertyDescriptor(Attributes)
*
* NOTE: to minimize code duplication, the code for this function is shared with
* that for Aux.4 NormalizeAndCompletePropertyDescriptor (see below). The
* argument complete is used to distinguish between the two.
*/
static bool
NormalizePropertyDescriptor(JSContext *cx, MutableHandleValue vp, bool complete = false)
{
// Aux.4 step 1
if (complete && vp.isUndefined())
return true;
// Aux.3 steps 1-2 / Aux.4 steps 2-3
Rooted<PropDesc> desc(cx);
if (!desc.initialize(cx, vp.get()))
return false;
if (complete)
desc.complete();
JS_ASSERT(vp.isObject()); // due to desc->initialize
RootedObject attributes(cx, &vp.toObject());
/*
* Aux.3 step 3 / Aux.4 step 4
*
* NOTE: Aux.4 step 4 actually specifies FromPropertyDescriptor here.
* However, the way FromPropertyDescriptor is implemented (PropDesc::
* makeObject) is actually closer to FromGenericPropertyDescriptor,
* and is in fact used to implement the latter, so we might as well call it
* directly.
*/
if (!FromGenericPropertyDescriptor(cx, &desc, vp))
return false;
if (vp.isUndefined())
return true;
RootedObject descObj(cx, &vp.toObject());
// Aux.3 steps 4-5 / Aux.4 steps 5-6
AutoIdVector props(cx);
if (!GetPropertyNames(cx, attributes, 0, &props))
return false;
size_t n = props.length();
for (size_t i = 0; i < n; ++i) {
RootedId id(cx, props[i]);
if (JSID_IS_ATOM(id)) {
JSAtom *atom = JSID_TO_ATOM(id);
const JSAtomState &atomState = cx->names();
if (atom == atomState.value || atom == atomState.writable ||
atom == atomState.get || atom == atomState.set ||
atom == atomState.enumerable || atom == atomState.configurable)
{
continue;
}
}
RootedValue v(cx);
if (!JSObject::getGeneric(cx, descObj, attributes, id, &v))
return false;
if (!JSObject::defineGeneric(cx, descObj, id, v, nullptr, nullptr, JSPROP_ENUMERATE))
return false;
}
return true;
}
// Aux.4 NormalizeAndCompletePropertyDescriptor(Attributes)
static inline bool
NormalizeAndCompletePropertyDescriptor(JSContext *cx, MutableHandleValue vp)
{
return NormalizePropertyDescriptor(cx, vp, true);
}
static inline bool
IsDataDescriptor(const PropertyDescriptor &desc)
{
return desc.obj && !(desc.attrs & (JSPROP_GETTER | JSPROP_SETTER));
}
static inline bool
IsAccessorDescriptor(const PropertyDescriptor &desc)
{
return desc.obj && desc.attrs & (JSPROP_GETTER | JSPROP_SETTER);
}
// ES6 (5 April 2014) ValidateAndApplyPropertyDescriptor(O, P, Extensible, Desc, Current)
// Since we are actually performing 9.1.6.2 IsCompatiblePropertyDescriptor(Extensible, Desc,
// Current), some parameters are omitted.
static bool
ValidatePropertyDescriptor(JSContext *cx, bool extensible, Handle<PropDesc> desc,
Handle<PropertyDescriptor> current, bool *bp)
{
// step 2
if (!current.object()) {
// Since |O| is always undefined, substeps c and d fall away.
*bp = extensible;
return true;
}
// step 3
if (!desc.hasValue() && !desc.hasWritable() && !desc.hasGet() && !desc.hasSet() &&
!desc.hasEnumerable() && !desc.hasConfigurable())
{
*bp = true;
return true;
}
// step 4
if ((!desc.hasWritable() || desc.writable() == !current.isReadonly()) &&
(!desc.hasGet() || desc.getter() == current.getter()) &&
(!desc.hasSet() || desc.setter() == current.setter()) &&
(!desc.hasEnumerable() || desc.enumerable() == current.isEnumerable()) &&
(!desc.hasConfigurable() || desc.configurable() == !current.isPermanent()))
{
if (!desc.hasValue()) {
*bp = true;
return true;
}
bool same = false;
if (!SameValue(cx, desc.value(), current.value(), &same))
return false;
if (same) {
*bp = true;
return true;
}
}
// step 5
if (current.isPermanent()) {
if (desc.hasConfigurable() && desc.configurable()) {
*bp = false;
return true;
}
if (desc.hasEnumerable() &&
desc.enumerable() != current.isEnumerable())
{
*bp = false;
return true;
}
}
// step 6
if (desc.isGenericDescriptor()) {
*bp = true;
return true;
}
// step 7a
if (IsDataDescriptor(current) != desc.isDataDescriptor()) {
*bp = !current.isPermanent();
return true;
}
// step 8
if (IsDataDescriptor(current)) {
JS_ASSERT(desc.isDataDescriptor()); // by step 7a
if (current.isPermanent() && current.isReadonly()) {
if (desc.hasWritable() && desc.writable()) {
*bp = false;
return true;
}
if (desc.hasValue()) {
bool same;
if (!SameValue(cx, desc.value(), current.value(), &same))
return false;
if (!same) {
*bp = false;
return true;
}
}
}
*bp = true;
return true;
}
// step 9
JS_ASSERT(IsAccessorDescriptor(current)); // by step 8
JS_ASSERT(desc.isAccessorDescriptor()); // by step 7a
*bp = (!current.isPermanent() ||
((!desc.hasSet() || desc.setter() == current.setter()) &&
(!desc.hasGet() || desc.getter() == current.getter())));
return true;
}
// Aux.6 IsSealed(O, P)
static bool
IsSealed(JSContext* cx, HandleObject obj, HandleId id, bool *bp)
{
// step 1
Rooted<PropertyDescriptor> desc(cx);
if (!GetOwnPropertyDescriptor(cx, obj, id, &desc))
return false;
// steps 2-3
*bp = desc.object() && desc.isPermanent();
return true;
}
static bool
HasOwn(JSContext *cx, HandleObject obj, HandleId id, bool *bp)
{
Rooted<PropertyDescriptor> desc(cx);
if (!JS_GetPropertyDescriptorById(cx, obj, id, &desc))
return false;
*bp = (desc.object() == obj);
return true;
}
static bool
IdToExposableValue(JSContext *cx, HandleId id, MutableHandleValue value)
{
value.set(IdToValue(id)); // Re-use out-param to avoid Rooted overhead.
JSString *name = ToString<CanGC>(cx, value);
if (!name)
return false;
value.set(StringValue(name));
return true;
}
// Get the [[ProxyHandler]] of a scripted direct proxy.
//
// NB: This *must* stay synched with proxy().
static JSObject *
GetDirectProxyHandlerObject(JSObject *proxy)
{
JS_ASSERT(proxy->as<ProxyObject>().handler() == &ScriptedDirectProxyHandler::singleton);
return proxy->as<ProxyObject>().extra(0).toObjectOrNull();
}
static inline void
ReportInvalidTrapResult(JSContext *cx, JSObject *proxy, JSAtom *atom)
{
RootedValue v(cx, ObjectOrNullValue(proxy));
JSAutoByteString bytes;
if (!AtomToPrintableString(cx, atom, &bytes))
return;
js_ReportValueError2(cx, JSMSG_INVALID_TRAP_RESULT, JSDVG_IGNORE_STACK, v,
js::NullPtr(), bytes.ptr());
}
// This function is shared between getOwnPropertyNames, enumerate, and keys
static bool
ArrayToIdVector(JSContext *cx, HandleObject proxy, HandleObject target, HandleValue v,
AutoIdVector &props, unsigned flags, JSAtom *trapName_)
{
JS_ASSERT(v.isObject());
RootedObject array(cx, &v.toObject());
RootedAtom trapName(cx, trapName_);
// steps g-h
uint32_t n;
if (!GetLengthProperty(cx, array, &n))
return false;
// steps i-k
for (uint32_t i = 0; i < n; ++i) {
// step i
RootedValue v(cx);
if (!JSObject::getElement(cx, array, array, i, &v))
return false;
// step ii
RootedId id(cx);
if (!ValueToId<CanGC>(cx, v, &id))
return false;
// step iii
for (uint32_t j = 0; j < i; ++j) {
if (props[j].get() == id) {
ReportInvalidTrapResult(cx, proxy, trapName);
return false;
}
}
// step iv
bool isFixed;
if (!HasOwn(cx, target, id, &isFixed))
return false;
// step v
bool extensible;
if (!JSObject::isExtensible(cx, target, &extensible))
return false;
if (!extensible && !isFixed) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_REPORT_NEW);
return false;
}
// step vi
if (!props.append(id))
return false;
}
// step l
AutoIdVector ownProps(cx);
if (!GetPropertyNames(cx, target, flags, &ownProps))
return false;
// step m
for (size_t i = 0; i < ownProps.length(); ++i) {
RootedId id(cx, ownProps[i]);
bool found = false;
for (size_t j = 0; j < props.length(); ++j) {
if (props[j].get() == id) {
found = true;
break;
}
}
if (found)
continue;
// step i
bool sealed;
if (!IsSealed(cx, target, id, &sealed))
return false;
if (sealed) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_SKIP_NC);
return false;
}
// step ii
bool isFixed;
if (!HasOwn(cx, target, id, &isFixed))
return false;
// step iii
bool extensible;
if (!JSObject::isExtensible(cx, target, &extensible))
return false;
if (!extensible && isFixed) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_REPORT_E_AS_NE);
return false;
}
}
// step n
return true;
}
ScriptedDirectProxyHandler::ScriptedDirectProxyHandler()
: DirectProxyHandler(&sScriptedDirectProxyHandlerFamily)
{
}
ScriptedDirectProxyHandler::~ScriptedDirectProxyHandler()
{
}
bool
ScriptedDirectProxyHandler::preventExtensions(JSContext *cx, HandleObject proxy)
{
// step a
RootedObject handler(cx, GetDirectProxyHandlerObject(proxy));
// step b
RootedObject target(cx, proxy->as<ProxyObject>().target());
// step c
RootedValue trap(cx);
if (!JSObject::getProperty(cx, handler, handler, cx->names().preventExtensions, &trap))
return false;
// step d
if (trap.isUndefined())
return DirectProxyHandler::preventExtensions(cx, proxy);
// step e
Value argv[] = {
ObjectValue(*target)
};
RootedValue trapResult(cx);
if (!Invoke(cx, ObjectValue(*handler), trap, ArrayLength(argv), argv, &trapResult))
return false;
// step f
bool success = ToBoolean(trapResult);
if (success) {
// step g
bool extensible;
if (!JSObject::isExtensible(cx, target, &extensible))
return false;
if (extensible) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_REPORT_AS_NON_EXTENSIBLE);
return false;
}
return true;
}
// step h
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_CHANGE_EXTENSIBILITY);
return false;
}
// FIXME: Move to Proxy::getPropertyDescriptor once ScriptedIndirectProxy is removed
bool
ScriptedDirectProxyHandler::getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
MutableHandle<PropertyDescriptor> desc)
{
JS_CHECK_RECURSION(cx, return false);
if (!GetOwnPropertyDescriptor(cx, proxy, id, desc))
return false;
if (desc.object())
return true;
RootedObject proto(cx);
if (!JSObject::getProto(cx, proxy, &proto))
return false;
if (!proto) {
JS_ASSERT(!desc.object());
return true;
}
return JS_GetPropertyDescriptorById(cx, proto, id, desc);
}
// ES6 (5 April 2014) Proxy.[[GetOwnProperty]](P)
bool
ScriptedDirectProxyHandler::getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
MutableHandle<PropertyDescriptor> desc)
{
// step 2
RootedObject handler(cx, GetDirectProxyHandlerObject(proxy));
// TODO: step 3: Implement revocation semantics. See bug 978279.
// step 4
RootedObject target(cx, proxy->as<ProxyObject>().target());
// step 5-6
RootedValue trap(cx);
if (!JSObject::getProperty(cx, handler, handler, cx->names().getOwnPropertyDescriptor, &trap))
return false;
// step 7
if (trap.isUndefined())
return DirectProxyHandler::getOwnPropertyDescriptor(cx, proxy, id, desc);
// step 8-9
RootedValue propKey(cx);
if (!IdToExposableValue(cx, id, &propKey))
return false;
Value argv[] = {
ObjectValue(*target),
propKey
};
RootedValue trapResult(cx);
if (!Invoke(cx, ObjectValue(*handler), trap, ArrayLength(argv), argv, &trapResult))
return false;
// step 10
if (!trapResult.isUndefined() && !trapResult.isObject()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_PROXY_GETOWN_OBJORUNDEF);
return false;
}
//step 11-12
Rooted<PropertyDescriptor> targetDesc(cx);
if (!GetOwnPropertyDescriptor(cx, target, id, &targetDesc))
return false;
// step 13
if (trapResult.isUndefined()) {
// substep a
if (!targetDesc.object()) {
desc.object().set(nullptr);
return true;
}
// substep b
if (targetDesc.isPermanent()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_REPORT_NC_AS_NE);
return false;
}
// substep c-e
bool extensibleTarget;
if (!JSObject::isExtensible(cx, target, &extensibleTarget))
return false;
if (!extensibleTarget) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_REPORT_E_AS_NE);
return false;
}
// substep f
desc.object().set(nullptr);
return true;
}
// step 14-15
bool extensibleTarget;
if (!JSObject::isExtensible(cx, target, &extensibleTarget))
return false;
// step 16-17
Rooted<PropDesc> resultDesc(cx);
if (!resultDesc.initialize(cx, trapResult))
return false;
// step 18
resultDesc.complete();
// step 19
bool valid;
if (!ValidatePropertyDescriptor(cx, extensibleTarget, resultDesc, targetDesc, &valid))
return false;
// step 20
if (!valid) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_REPORT_INVALID);
return false;
}
// step 21
if (!resultDesc.configurable()) {
if (!targetDesc.object()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_REPORT_NE_AS_NC);
return false;
}
if (!targetDesc.isPermanent()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_REPORT_C_AS_NC);
return false;
}
}
// step 22
// FIXME: This is incorrect with respect to [[Origin]]. See bug 999156.
resultDesc.populatePropertyDescriptor(proxy, desc);
return true;
}
// ES6 (5 April 2014) Proxy.[[DefineOwnProperty]](O,P)
bool
ScriptedDirectProxyHandler::defineProperty(JSContext *cx, HandleObject proxy, HandleId id,
MutableHandle<PropertyDescriptor> desc)
{
// step 2
RootedObject handler(cx, GetDirectProxyHandlerObject(proxy));
// TODO: step 3: Implement revocation semantics. See bug 978279.
// step 4
RootedObject target(cx, proxy->as<ProxyObject>().target());
// step 5-6
RootedValue trap(cx);
if (!JSObject::getProperty(cx, handler, handler, cx->names().defineProperty, &trap))
return false;
// step 7
if (trap.isUndefined())
return DirectProxyHandler::defineProperty(cx, proxy, id, desc);
// step 8-9, with 9 blatantly defied.
// FIXME: This is incorrect with respect to [[Origin]]. See bug 601379.
RootedValue descObj(cx);
if (!NewPropertyDescriptorObject(cx, desc, &descObj))
return false;
// step 10, 12
RootedValue propKey(cx);
if (!IdToExposableValue(cx, id, &propKey))
return false;
Value argv[] = {
ObjectValue(*target),
propKey,
descObj
};
RootedValue trapResult(cx);
if (!Invoke(cx, ObjectValue(*handler), trap, ArrayLength(argv), argv, &trapResult))
return false;
// step 11, 13
if (ToBoolean(trapResult)) {
// step 14-15
Rooted<PropertyDescriptor> targetDesc(cx);
if (!GetOwnPropertyDescriptor(cx, target, id, &targetDesc))
return false;
// step 16-17
bool extensibleTarget;
if (!JSObject::isExtensible(cx, target, &extensibleTarget))
return false;
// step 18-19
bool settingConfigFalse = desc.isPermanent();
if (!targetDesc.object()) {
// step 20a
if (!extensibleTarget) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_DEFINE_NEW);
return false;
}
// step 20b
if (settingConfigFalse) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_DEFINE_NE_AS_NC);
return false;
}
} else {
// step 21
bool valid;
Rooted<PropDesc> pd(cx);
pd.initFromPropertyDescriptor(desc);
if (!ValidatePropertyDescriptor(cx, extensibleTarget, pd, targetDesc, &valid))
return false;
if (!valid || (settingConfigFalse && !targetDesc.isPermanent())) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_DEFINE_INVALID);
return false;
}
}
}
// [[DefineProperty]] should return a boolean value, which is used to do things like
// strict-mode throwing. At present, the engine is not prepared to do that. See bug 826587.
return true;
}
bool
ScriptedDirectProxyHandler::getOwnPropertyNames(JSContext *cx, HandleObject proxy,
AutoIdVector &props)
{
// step a
RootedObject handler(cx, GetDirectProxyHandlerObject(proxy));
// step b
RootedObject target(cx, proxy->as<ProxyObject>().target());
// step c
RootedValue trap(cx);
if (!JSObject::getProperty(cx, handler, handler, cx->names().getOwnPropertyNames, &trap))
return false;
// step d
if (trap.isUndefined())
return DirectProxyHandler::getOwnPropertyNames(cx, proxy, props);
// step e
Value argv[] = {
ObjectValue(*target)
};
RootedValue trapResult(cx);
if (!Invoke(cx, ObjectValue(*handler), trap, ArrayLength(argv), argv, &trapResult))
return false;
// step f
if (trapResult.isPrimitive()) {
ReportInvalidTrapResult(cx, proxy, cx->names().getOwnPropertyNames);
return false;
}
// steps g to n are shared
return ArrayToIdVector(cx, proxy, target, trapResult, props, JSITER_OWNONLY | JSITER_HIDDEN,
cx->names().getOwnPropertyNames);
}
// ES6 (5 April 2014) Proxy.[[Delete]](P)
bool
ScriptedDirectProxyHandler::delete_(JSContext *cx, HandleObject proxy, HandleId id, bool *bp)
{
// step 2
RootedObject handler(cx, GetDirectProxyHandlerObject(proxy));
// TODO: step 3: Implement revocation semantics
// step 4
RootedObject target(cx, proxy->as<ProxyObject>().target());
// step 5
RootedValue trap(cx);
if (!JSObject::getProperty(cx, handler, handler, cx->names().deleteProperty, &trap))
return false;
// step 7
if (trap.isUndefined())
return DirectProxyHandler::delete_(cx, proxy, id, bp);
// step 8
RootedValue value(cx);
if (!IdToExposableValue(cx, id, &value))
return false;
Value argv[] = {
ObjectValue(*target),
value
};
RootedValue trapResult(cx);
if (!Invoke(cx, ObjectValue(*handler), trap, ArrayLength(argv), argv, &trapResult))
return false;
// step 9
if (ToBoolean(trapResult)) {
// step 12
Rooted<PropertyDescriptor> desc(cx);
if (!GetOwnPropertyDescriptor(cx, target, id, &desc))
return false;
// step 14-15
if (desc.object() && desc.isPermanent()) {
RootedValue v(cx, IdToValue(id));
js_ReportValueError(cx, JSMSG_CANT_DELETE, JSDVG_IGNORE_STACK, v, js::NullPtr());
return false;
}
// step 16
*bp = true;
return true;
}
// step 11
*bp = false;
return true;
}
// 12.6.4 The for-in Statement, step 6
bool
ScriptedDirectProxyHandler::enumerate(JSContext *cx, HandleObject proxy, AutoIdVector &props)
{
// step a
RootedObject handler(cx, GetDirectProxyHandlerObject(proxy));
// step b
RootedObject target(cx, proxy->as<ProxyObject>().target());
// step c
RootedValue trap(cx);
if (!JSObject::getProperty(cx, handler, handler, cx->names().enumerate, &trap))
return false;
// step d
if (trap.isUndefined())
return DirectProxyHandler::enumerate(cx, proxy, props);
// step e
Value argv[] = {
ObjectOrNullValue(target)
};
RootedValue trapResult(cx);
if (!Invoke(cx, ObjectValue(*handler), trap, ArrayLength(argv), argv, &trapResult))
return false;
// step f
if (trapResult.isPrimitive()) {
JSAutoByteString bytes;
if (!AtomToPrintableString(cx, cx->names().enumerate, &bytes))
return false;
RootedValue v(cx, ObjectOrNullValue(proxy));
js_ReportValueError2(cx, JSMSG_INVALID_TRAP_RESULT, JSDVG_SEARCH_STACK,
v, js::NullPtr(), bytes.ptr());
return false;
}
// steps g-m are shared
// FIXME: the trap should return an iterator object, see bug 783826
return ArrayToIdVector(cx, proxy, target, trapResult, props, 0, cx->names().enumerate);
}
// Proxy.[[HasProperty]](P)
bool
ScriptedDirectProxyHandler::has(JSContext *cx, HandleObject proxy, HandleId id, bool *bp)
{
// step 1
RootedObject handler(cx, GetDirectProxyHandlerObject(proxy));
// step 2
RootedObject target(cx, proxy->as<ProxyObject>().target());
// step 3
RootedValue trap(cx);
if (!JSObject::getProperty(cx, handler, handler, cx->names().has, &trap))
return false;
// step 4
if (trap.isUndefined())
return DirectProxyHandler::has(cx, proxy, id, bp);
// step 5
RootedValue value(cx);
if (!IdToExposableValue(cx, id, &value))
return false;
Value argv[] = {
ObjectOrNullValue(target),
value
};
RootedValue trapResult(cx);
if (!Invoke(cx, ObjectValue(*handler), trap, ArrayLength(argv), argv, &trapResult))
return false;
// step 6
bool success = ToBoolean(trapResult);;
// step 7
if (!success) {
Rooted<PropertyDescriptor> desc(cx);
if (!GetOwnPropertyDescriptor(cx, target, id, &desc))
return false;
if (desc.object()) {
if (desc.isPermanent()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_REPORT_NC_AS_NE);
return false;
}
bool extensible;
if (!JSObject::isExtensible(cx, target, &extensible))
return false;
if (!extensible) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_REPORT_E_AS_NE);
return false;
}
}
}
// step 8
*bp = success;
return true;
}
// Proxy.[[HasOwnProperty]](P)
bool
ScriptedDirectProxyHandler::hasOwn(JSContext *cx, HandleObject proxy, HandleId id, bool *bp)
{
// step 1
RootedObject handler(cx, GetDirectProxyHandlerObject(proxy));
// step 2
RootedObject target(cx, proxy->as<ProxyObject>().target());
// step 3
RootedValue trap(cx);
if (!JSObject::getProperty(cx, handler, handler, cx->names().hasOwn, &trap))
return false;
// step 4
if (trap.isUndefined())
return DirectProxyHandler::hasOwn(cx, proxy, id, bp);
// step 5
RootedValue value(cx);
if (!IdToExposableValue(cx, id, &value))
return false;
Value argv[] = {
ObjectOrNullValue(target),
value
};
RootedValue trapResult(cx);
if (!Invoke(cx, ObjectValue(*handler), trap, ArrayLength(argv), argv, &trapResult))
return false;
// step 6
bool success = ToBoolean(trapResult);
// steps 7-8
if (!success) {
bool sealed;
if (!IsSealed(cx, target, id, &sealed))
return false;
if (sealed) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_REPORT_NC_AS_NE);
return false;
}
bool extensible;
if (!JSObject::isExtensible(cx, target, &extensible))
return false;
if (!extensible) {
bool isFixed;
if (!HasOwn(cx, target, id, &isFixed))
return false;
if (isFixed) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_REPORT_E_AS_NE);
return false;
}
}
} else {
bool extensible;
if (!JSObject::isExtensible(cx, target, &extensible))
return false;
if (!extensible) {
bool isFixed;
if (!HasOwn(cx, target, id, &isFixed))
return false;
if (!isFixed) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_REPORT_NEW);
return false;
}
}
}
// step 9
*bp = !!success;
return true;
}
// Proxy.[[GetP]](P, Receiver)
bool
ScriptedDirectProxyHandler::get(JSContext *cx, HandleObject proxy, HandleObject receiver,
HandleId id, MutableHandleValue vp)
{
// step 1
RootedObject handler(cx, GetDirectProxyHandlerObject(proxy));
// step 2
RootedObject target(cx, proxy->as<ProxyObject>().target());
// step 3
RootedValue trap(cx);
if (!JSObject::getProperty(cx, handler, handler, cx->names().get, &trap))
return false;
// step 4
if (trap.isUndefined())
return DirectProxyHandler::get(cx, proxy, receiver, id, vp);
// step 5
RootedValue value(cx);
if (!IdToExposableValue(cx, id, &value))
return false;
Value argv[] = {
ObjectOrNullValue(target),
value,
ObjectOrNullValue(receiver)
};
RootedValue trapResult(cx);
if (!Invoke(cx, ObjectValue(*handler), trap, ArrayLength(argv), argv, &trapResult))
return false;
// step 6
Rooted<PropertyDescriptor> desc(cx);
if (!GetOwnPropertyDescriptor(cx, target, id, &desc))
return false;
// step 7
if (desc.object()) {
if (IsDataDescriptor(desc) && desc.isPermanent() && desc.isReadonly()) {
bool same;
if (!SameValue(cx, trapResult, desc.value(), &same))
return false;
if (!same) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MUST_REPORT_SAME_VALUE);
return false;
}
}
if (IsAccessorDescriptor(desc) && desc.isPermanent() && !desc.hasGetterObject()) {
if (!trapResult.isUndefined()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MUST_REPORT_UNDEFINED);
return false;
}
}
}
// step 8
vp.set(trapResult);
return true;
}
// Proxy.[[SetP]](P, V, Receiver)
bool
ScriptedDirectProxyHandler::set(JSContext *cx, HandleObject proxy, HandleObject receiver,
HandleId id, bool strict, MutableHandleValue vp)
{
// step 1
RootedObject handler(cx, GetDirectProxyHandlerObject(proxy));
// step 2
RootedObject target(cx, proxy->as<ProxyObject>().target());
// step 3
RootedValue trap(cx);
if (!JSObject::getProperty(cx, handler, handler, cx->names().set, &trap))
return false;
// step 4
if (trap.isUndefined())
return DirectProxyHandler::set(cx, proxy, receiver, id, strict, vp);
// step 5
RootedValue value(cx);
if (!IdToExposableValue(cx, id, &value))
return false;
Value argv[] = {
ObjectOrNullValue(target),
value,
vp.get(),
ObjectValue(*receiver)
};
RootedValue trapResult(cx);
if (!Invoke(cx, ObjectValue(*handler), trap, ArrayLength(argv), argv, &trapResult))
return false;
// step 6
bool success = ToBoolean(trapResult);
// step 7
if (success) {
Rooted<PropertyDescriptor> desc(cx);
if (!GetOwnPropertyDescriptor(cx, target, id, &desc))
return false;
if (desc.object()) {
if (IsDataDescriptor(desc) && desc.isPermanent() && desc.isReadonly()) {
bool same;
if (!SameValue(cx, vp, desc.value(), &same))
return false;
if (!same) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_SET_NW_NC);
return false;
}
}
if (IsAccessorDescriptor(desc) && desc.isPermanent() && !desc.hasSetterObject()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_SET_WO_SETTER);
return false;
}
}
}
// step 8
vp.set(BooleanValue(success));
return true;
}
// 15.2.3.14 Object.keys (O), step 2
bool
ScriptedDirectProxyHandler::keys(JSContext *cx, HandleObject proxy, AutoIdVector &props)
{
// step a
RootedObject handler(cx, GetDirectProxyHandlerObject(proxy));
// step b
RootedObject target(cx, proxy->as<ProxyObject>().target());
// step c
RootedValue trap(cx);
if (!JSObject::getProperty(cx, handler, handler, cx->names().keys, &trap))
return false;
// step d
if (trap.isUndefined())
return DirectProxyHandler::keys(cx, proxy, props);
// step e
Value argv[] = {
ObjectOrNullValue(target)
};
RootedValue trapResult(cx);
if (!Invoke(cx, ObjectValue(*handler), trap, ArrayLength(argv), argv, &trapResult))
return false;
// step f
if (trapResult.isPrimitive()) {
JSAutoByteString bytes;
if (!AtomToPrintableString(cx, cx->names().keys, &bytes))
return false;
RootedValue v(cx, ObjectOrNullValue(proxy));
js_ReportValueError2(cx, JSMSG_INVALID_TRAP_RESULT, JSDVG_IGNORE_STACK,
v, js::NullPtr(), bytes.ptr());
return false;
}
// steps g-n are shared
return ArrayToIdVector(cx, proxy, target, trapResult, props, JSITER_OWNONLY, cx->names().keys);
}
// ES6 (5 April, 2014) 9.5.3 Proxy.[[IsExtensible]](P)
bool
ScriptedDirectProxyHandler::isExtensible(JSContext *cx, HandleObject proxy, bool *extensible)
{
RootedObject handler(cx, GetDirectProxyHandlerObject(proxy));
RootedObject target(cx, proxy->as<ProxyObject>().target());
RootedValue trap(cx);
if (!JSObject::getProperty(cx, handler, handler, cx->names().isExtensible, &trap))
return false;
if (trap.isUndefined())
return DirectProxyHandler::isExtensible(cx, proxy, extensible);
Value argv[] = {
ObjectValue(*target)
};
RootedValue trapResult(cx);
if (!Invoke(cx, ObjectValue(*handler), trap, ArrayLength(argv), argv, &trapResult))
return false;
bool booleanTrapResult = ToBoolean(trapResult);
bool targetResult;
if (!JSObject::isExtensible(cx, target, &targetResult))
return false;
if (targetResult != booleanTrapResult) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_PROXY_EXTENSIBILITY);
return false;
}
*extensible = booleanTrapResult;
return true;
}
bool
ScriptedDirectProxyHandler::iterate(JSContext *cx, HandleObject proxy, unsigned flags,
MutableHandleValue vp)
{
// FIXME: Provide a proper implementation for this trap, see bug 787004
return DirectProxyHandler::iterate(cx, proxy, flags, vp);
}
bool
ScriptedDirectProxyHandler::call(JSContext *cx, HandleObject proxy, const CallArgs &args)
{
// step 1
RootedObject handler(cx, GetDirectProxyHandlerObject(proxy));
// step 2
RootedObject target(cx, proxy->as<ProxyObject>().target());
/*
* NB: Remember to throw a TypeError here if we change NewProxyObject so that this trap can get
* called for non-callable objects
*/
// step 3
RootedObject argsArray(cx, NewDenseCopiedArray(cx, args.length(), args.array()));
if (!argsArray)
return false;
// step 4
RootedValue trap(cx);
if (!JSObject::getProperty(cx, handler, handler, cx->names().apply, &trap))
return false;
// step 5
if (trap.isUndefined())
return DirectProxyHandler::call(cx, proxy, args);
// step 6
Value argv[] = {
ObjectValue(*target),
args.thisv(),
ObjectValue(*argsArray)
};
RootedValue thisValue(cx, ObjectValue(*handler));
return Invoke(cx, thisValue, trap, ArrayLength(argv), argv, args.rval());
}
bool
ScriptedDirectProxyHandler::construct(JSContext *cx, HandleObject proxy, const CallArgs &args)
{
// step 1
RootedObject handler(cx, GetDirectProxyHandlerObject(proxy));
// step 2
RootedObject target(cx, proxy->as<ProxyObject>().target());
/*
* NB: Remember to throw a TypeError here if we change NewProxyObject so that this trap can get
* called for non-callable objects
*/
// step 3
RootedObject argsArray(cx, NewDenseCopiedArray(cx, args.length(), args.array()));
if (!argsArray)
return false;
// step 4
RootedValue trap(cx);
if (!JSObject::getProperty(cx, handler, handler, cx->names().construct, &trap))
return false;
// step 5
if (trap.isUndefined())
return DirectProxyHandler::construct(cx, proxy, args);
// step 6
Value constructArgv[] = {
ObjectValue(*target),
ObjectValue(*argsArray)
};
RootedValue thisValue(cx, ObjectValue(*handler));
if (!Invoke(cx, thisValue, trap, ArrayLength(constructArgv), constructArgv, args.rval()))
return false;
if (!args.rval().isObject()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_PROXY_CONSTRUCT_OBJECT);
return false;
}
return true;
}
ScriptedDirectProxyHandler ScriptedDirectProxyHandler::singleton;
#define INVOKE_ON_PROTOTYPE(cx, handler, proxy, protoCall) \
JS_BEGIN_MACRO \
RootedObject proto(cx); \
if (!JSObject::getProto(cx, proxy, &proto)) \
return false; \
if (!proto) \
return true; \
assertSameCompartment(cx, proxy, proto); \
return protoCall; \
JS_END_MACRO \
bool
Proxy::getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
MutableHandle<PropertyDescriptor> desc)
{
JS_CHECK_RECURSION(cx, return false);
BaseProxyHandler *handler = proxy->as<ProxyObject>().handler();
desc.object().set(nullptr); // default result if we refuse to perform this action
AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true);
if (!policy.allowed())
return policy.returnValue();
if (!handler->hasPrototype())
return handler->getPropertyDescriptor(cx, proxy, id, desc);
if (!handler->getOwnPropertyDescriptor(cx, proxy, id, desc))
return false;
if (desc.object())
return true;
INVOKE_ON_PROTOTYPE(cx, handler, proxy, JS_GetPropertyDescriptorById(cx, proto, id, desc));
}
bool
Proxy::getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id, MutableHandleValue vp)
{
JS_CHECK_RECURSION(cx, return false);
Rooted<PropertyDescriptor> desc(cx);
if (!Proxy::getPropertyDescriptor(cx, proxy, id, &desc))
return false;
return NewPropertyDescriptorObject(cx, desc, vp);
}
bool
Proxy::getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
MutableHandle<PropertyDescriptor> desc)
{
JS_CHECK_RECURSION(cx, return false);
BaseProxyHandler *handler = proxy->as<ProxyObject>().handler();
desc.object().set(nullptr); // default result if we refuse to perform this action
AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true);
if (!policy.allowed())
return policy.returnValue();
return handler->getOwnPropertyDescriptor(cx, proxy, id, desc);
}
bool
Proxy::getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
MutableHandleValue vp)
{
JS_CHECK_RECURSION(cx, return false);
Rooted<PropertyDescriptor> desc(cx);
if (!Proxy::getOwnPropertyDescriptor(cx, proxy, id, &desc))
return false;
return NewPropertyDescriptorObject(cx, desc, vp);
}
bool
Proxy::defineProperty(JSContext *cx, HandleObject proxy, HandleId id,
MutableHandle<PropertyDescriptor> desc)
{
JS_CHECK_RECURSION(cx, return false);
BaseProxyHandler *handler = proxy->as<ProxyObject>().handler();
AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true);
if (!policy.allowed())
return policy.returnValue();
return proxy->as<ProxyObject>().handler()->defineProperty(cx, proxy, id, desc);
}
bool
Proxy::getOwnPropertyNames(JSContext *cx, HandleObject proxy, AutoIdVector &props)
{
JS_CHECK_RECURSION(cx, return false);
BaseProxyHandler *handler = proxy->as<ProxyObject>().handler();
AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::ENUMERATE, true);
if (!policy.allowed())
return policy.returnValue();
return proxy->as<ProxyObject>().handler()->getOwnPropertyNames(cx, proxy, props);
}
bool
Proxy::delete_(JSContext *cx, HandleObject proxy, HandleId id, bool *bp)
{
JS_CHECK_RECURSION(cx, return false);
BaseProxyHandler *handler = proxy->as<ProxyObject>().handler();
*bp = true; // default result if we refuse to perform this action
AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true);
if (!policy.allowed())
return policy.returnValue();
return proxy->as<ProxyObject>().handler()->delete_(cx, proxy, id, bp);
}
JS_FRIEND_API(bool)
js::AppendUnique(JSContext *cx, AutoIdVector &base, AutoIdVector &others)
{
AutoIdVector uniqueOthers(cx);
if (!uniqueOthers.reserve(others.length()))
return false;
for (size_t i = 0; i < others.length(); ++i) {
bool unique = true;
for (size_t j = 0; j < base.length(); ++j) {
if (others[i].get() == base[j]) {
unique = false;
break;
}
}
if (unique)
uniqueOthers.append(others[i]);
}
return base.appendAll(uniqueOthers);
}
bool
Proxy::enumerate(JSContext *cx, HandleObject proxy, AutoIdVector &props)
{
JS_CHECK_RECURSION(cx, return false);
BaseProxyHandler *handler = proxy->as<ProxyObject>().handler();
AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::ENUMERATE, true);
if (!policy.allowed())
return policy.returnValue();
if (!handler->hasPrototype())
return proxy->as<ProxyObject>().handler()->enumerate(cx, proxy, props);
if (!handler->keys(cx, proxy, props))
return false;
AutoIdVector protoProps(cx);
INVOKE_ON_PROTOTYPE(cx, handler, proxy,
GetPropertyNames(cx, proto, 0, &protoProps) &&
AppendUnique(cx, props, protoProps));
}
bool
Proxy::has(JSContext *cx, HandleObject proxy, HandleId id, bool *bp)
{
JS_CHECK_RECURSION(cx, return false);
BaseProxyHandler *handler = proxy->as<ProxyObject>().handler();
*bp = false; // default result if we refuse to perform this action
AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true);
if (!policy.allowed())
return policy.returnValue();
if (!handler->hasPrototype())
return handler->has(cx, proxy, id, bp);
if (!handler->hasOwn(cx, proxy, id, bp))
return false;
if (*bp)
return true;
bool Bp;
INVOKE_ON_PROTOTYPE(cx, handler, proxy,
JS_HasPropertyById(cx, proto, id, &Bp) &&
((*bp = Bp) || true));
}
bool
Proxy::hasOwn(JSContext *cx, HandleObject proxy, HandleId id, bool *bp)
{
JS_CHECK_RECURSION(cx, return false);
BaseProxyHandler *handler = proxy->as<ProxyObject>().handler();
*bp = false; // default result if we refuse to perform this action
AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true);
if (!policy.allowed())
return policy.returnValue();
return handler->hasOwn(cx, proxy, id, bp);
}
bool
Proxy::get(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id,
MutableHandleValue vp)
{
JS_CHECK_RECURSION(cx, return false);
BaseProxyHandler *handler = proxy->as<ProxyObject>().handler();
vp.setUndefined(); // default result if we refuse to perform this action
AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true);
if (!policy.allowed())
return policy.returnValue();
bool own;
if (!handler->hasPrototype()) {
own = true;
} else {
if (!handler->hasOwn(cx, proxy, id, &own))
return false;
}
if (own)
return handler->get(cx, proxy, receiver, id, vp);
INVOKE_ON_PROTOTYPE(cx, handler, proxy, JSObject::getGeneric(cx, proto, receiver, id, vp));
}
bool
Proxy::callProp(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id,
MutableHandleValue vp)
{
// The inline caches need an access point for JSOP_CALLPROP sites that accounts
// for the possibility of __noSuchMethod__
if (!Proxy::get(cx, proxy, receiver, id, vp))
return false;
#if JS_HAS_NO_SUCH_METHOD
if (MOZ_UNLIKELY(vp.isPrimitive())) {
if (!OnUnknownMethod(cx, proxy, IdToValue(id), vp))
return false;
}
#endif
return true;
}
bool
Proxy::set(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id, bool strict,
MutableHandleValue vp)
{
JS_CHECK_RECURSION(cx, return false);
BaseProxyHandler *handler = proxy->as<ProxyObject>().handler();
AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true);
if (!policy.allowed())
return policy.returnValue();
// If the proxy doesn't require that we consult its prototype for the
// non-own cases, we can sink to the |set| trap.
if (!handler->hasPrototype())
return handler->set(cx, proxy, receiver, id, strict, vp);
// If we have an existing (own or non-own) property with a setter, we want
// to invoke that.
Rooted<PropertyDescriptor> desc(cx);
if (!Proxy::getPropertyDescriptor(cx, proxy, id, &desc))
return false;
if (desc.object() && desc.setter() && desc.setter() != JS_StrictPropertyStub)
return CallSetter(cx, receiver, id, desc.setter(), desc.attributes(), strict, vp);
// Ok. Either there was no pre-existing property, or it was a value prop
// that we're going to shadow. Make a property descriptor and define it.
Rooted<PropertyDescriptor> newDesc(cx);
newDesc.value().set(vp);
newDesc.setAttributes(JSPROP_ENUMERATE);
return handler->defineProperty(cx, receiver, id, &newDesc);
}
bool
Proxy::keys(JSContext *cx, HandleObject proxy, AutoIdVector &props)
{
JS_CHECK_RECURSION(cx, return false);
BaseProxyHandler *handler = proxy->as<ProxyObject>().handler();
AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::ENUMERATE, true);
if (!policy.allowed())
return policy.returnValue();
return handler->keys(cx, proxy, props);
}
bool
Proxy::iterate(JSContext *cx, HandleObject proxy, unsigned flags, MutableHandleValue vp)
{
JS_CHECK_RECURSION(cx, return false);
BaseProxyHandler *handler = proxy->as<ProxyObject>().handler();
vp.setUndefined(); // default result if we refuse to perform this action
if (!handler->hasPrototype()) {
AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE,
BaseProxyHandler::ENUMERATE, true);
// If the policy denies access but wants us to return true, we need
// to hand a valid (empty) iterator object to the caller.
if (!policy.allowed()) {
AutoIdVector props(cx);
return policy.returnValue() &&
EnumeratedIdVectorToIterator(cx, proxy, flags, props, vp);
}
return handler->iterate(cx, proxy, flags, vp);
}
AutoIdVector props(cx);
// The other Proxy::foo methods do the prototype-aware work for us here.
if ((flags & JSITER_OWNONLY)
? !Proxy::keys(cx, proxy, props)
: !Proxy::enumerate(cx, proxy, props)) {
return false;
}
return EnumeratedIdVectorToIterator(cx, proxy, flags, props, vp);
}
bool
Proxy::isExtensible(JSContext *cx, HandleObject proxy, bool *extensible)
{
JS_CHECK_RECURSION(cx, return false);
return proxy->as<ProxyObject>().handler()->isExtensible(cx, proxy, extensible);
}
bool
Proxy::preventExtensions(JSContext *cx, HandleObject proxy)
{
JS_CHECK_RECURSION(cx, return false);
BaseProxyHandler *handler = proxy->as<ProxyObject>().handler();
return handler->preventExtensions(cx, proxy);
}
bool
Proxy::call(JSContext *cx, HandleObject proxy, const CallArgs &args)
{
JS_CHECK_RECURSION(cx, return false);
BaseProxyHandler *handler = proxy->as<ProxyObject>().handler();
// Because vp[0] is JS_CALLEE on the way in and JS_RVAL on the way out, we
// can only set our default value once we're sure that we're not calling the
// trap.
AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE,
BaseProxyHandler::CALL, true);
if (!policy.allowed()) {
args.rval().setUndefined();
return policy.returnValue();
}
return handler->call(cx, proxy, args);
}
bool
Proxy::construct(JSContext *cx, HandleObject proxy, const CallArgs &args)
{
JS_CHECK_RECURSION(cx, return false);
BaseProxyHandler *handler = proxy->as<ProxyObject>().handler();
// Because vp[0] is JS_CALLEE on the way in and JS_RVAL on the way out, we
// can only set our default value once we're sure that we're not calling the
// trap.
AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE,
BaseProxyHandler::CALL, true);
if (!policy.allowed()) {
args.rval().setUndefined();
return policy.returnValue();
}
return handler->construct(cx, proxy, args);
}
bool
Proxy::nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl, CallArgs args)
{
JS_CHECK_RECURSION(cx, return false);
RootedObject proxy(cx, &args.thisv().toObject());
// Note - we don't enter a policy here because our security architecture
// guards against nativeCall by overriding the trap itself in the right
// circumstances.
return proxy->as<ProxyObject>().handler()->nativeCall(cx, test, impl, args);
}
bool
Proxy::hasInstance(JSContext *cx, HandleObject proxy, MutableHandleValue v, bool *bp)
{
JS_CHECK_RECURSION(cx, return false);
BaseProxyHandler *handler = proxy->as<ProxyObject>().handler();
*bp = false; // default result if we refuse to perform this action
AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::GET, true);
if (!policy.allowed())
return policy.returnValue();
return proxy->as<ProxyObject>().handler()->hasInstance(cx, proxy, v, bp);
}
bool
Proxy::objectClassIs(HandleObject proxy, ESClassValue classValue, JSContext *cx)
{
JS_CHECK_RECURSION(cx, return false);
return proxy->as<ProxyObject>().handler()->objectClassIs(proxy, classValue, cx);
}
const char *
Proxy::className(JSContext *cx, HandleObject proxy)
{
// Check for unbounded recursion, but don't signal an error; className
// needs to be infallible.
int stackDummy;
if (!JS_CHECK_STACK_SIZE(GetNativeStackLimit(cx), &stackDummy))
return "too much recursion";
BaseProxyHandler *handler = proxy->as<ProxyObject>().handler();
AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE,
BaseProxyHandler::GET, /* mayThrow = */ false);
// Do the safe thing if the policy rejects.
if (!policy.allowed()) {
return handler->BaseProxyHandler::className(cx, proxy);
}
return handler->className(cx, proxy);
}
JSString *
Proxy::fun_toString(JSContext *cx, HandleObject proxy, unsigned indent)
{
JS_CHECK_RECURSION(cx, return nullptr);
BaseProxyHandler *handler = proxy->as<ProxyObject>().handler();
AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE,
BaseProxyHandler::GET, /* mayThrow = */ false);
// Do the safe thing if the policy rejects.
if (!policy.allowed())
return handler->BaseProxyHandler::fun_toString(cx, proxy, indent);
return handler->fun_toString(cx, proxy, indent);
}
bool
Proxy::regexp_toShared(JSContext *cx, HandleObject proxy, RegExpGuard *g)
{
JS_CHECK_RECURSION(cx, return false);
return proxy->as<ProxyObject>().handler()->regexp_toShared(cx, proxy, g);
}
bool
Proxy::defaultValue(JSContext *cx, HandleObject proxy, JSType hint, MutableHandleValue vp)
{
JS_CHECK_RECURSION(cx, return false);
return proxy->as<ProxyObject>().handler()->defaultValue(cx, proxy, hint, vp);
}
JSObject * const TaggedProto::LazyProto = reinterpret_cast<JSObject *>(0x1);
bool
Proxy::getPrototypeOf(JSContext *cx, HandleObject proxy, MutableHandleObject proto)
{
JS_ASSERT(proxy->getTaggedProto().isLazy());
JS_CHECK_RECURSION(cx, return false);
return proxy->as<ProxyObject>().handler()->getPrototypeOf(cx, proxy, proto);
}
bool
Proxy::setPrototypeOf(JSContext *cx, HandleObject proxy, HandleObject proto, bool *bp)
{
JS_ASSERT(proxy->getTaggedProto().isLazy());
JS_CHECK_RECURSION(cx, return false);
return proxy->as<ProxyObject>().handler()->setPrototypeOf(cx, proxy, proto, bp);
}
/* static */ bool
Proxy::watch(JSContext *cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleObject callable)
{
JS_CHECK_RECURSION(cx, return false);
return proxy->as<ProxyObject>().handler()->watch(cx, proxy, id, callable);
}
/* static */ bool
Proxy::unwatch(JSContext *cx, JS::HandleObject proxy, JS::HandleId id)
{
JS_CHECK_RECURSION(cx, return false);
return proxy->as<ProxyObject>().handler()->unwatch(cx, proxy, id);
}
/* static */ bool
Proxy::slice(JSContext *cx, HandleObject proxy, uint32_t begin, uint32_t end,
HandleObject result)
{
JS_CHECK_RECURSION(cx, return false);
BaseProxyHandler *handler = proxy->as<ProxyObject>().handler();
AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::GET,
/* mayThrow = */ true);
if (!policy.allowed()) {
if (policy.returnValue()) {
JS_ASSERT(!cx->isExceptionPending());
return js::SliceSlowly(cx, proxy, proxy, begin, end, result);
}
return false;
}
return handler->slice(cx, proxy, begin, end, result);
}
JSObject *
js::proxy_innerObject(JSObject *obj)
{
return obj->as<ProxyObject>().private_().toObjectOrNull();
}
bool
js::proxy_LookupGeneric(JSContext *cx, HandleObject obj, HandleId id,
MutableHandleObject objp, MutableHandleShape propp)
{
bool found;
if (!Proxy::has(cx, obj, id, &found))
return false;
if (found) {
MarkNonNativePropertyFound(propp);
objp.set(obj);
} else {
objp.set(nullptr);
propp.set(nullptr);
}
return true;
}
bool
js::proxy_LookupProperty(JSContext *cx, HandleObject obj, HandlePropertyName name,
MutableHandleObject objp, MutableHandleShape propp)
{
RootedId id(cx, NameToId(name));
return proxy_LookupGeneric(cx, obj, id, objp, propp);
}
bool
js::proxy_LookupElement(JSContext *cx, HandleObject obj, uint32_t index,
MutableHandleObject objp, MutableHandleShape propp)
{
RootedId id(cx);
if (!IndexToId(cx, index, &id))
return false;
return proxy_LookupGeneric(cx, obj, id, objp, propp);
}
bool
js::proxy_DefineGeneric(JSContext *cx, HandleObject obj, HandleId id, HandleValue value,
PropertyOp getter, StrictPropertyOp setter, unsigned attrs)
{
Rooted<PropertyDescriptor> desc(cx);
desc.object().set(obj);
desc.value().set(value);
desc.setAttributes(attrs);
desc.setGetter(getter);
desc.setSetter(setter);
return Proxy::defineProperty(cx, obj, id, &desc);
}
bool
js::proxy_DefineProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, HandleValue value,
PropertyOp getter, StrictPropertyOp setter, unsigned attrs)
{
Rooted<jsid> id(cx, NameToId(name));
return proxy_DefineGeneric(cx, obj, id, value, getter, setter, attrs);
}
bool
js::proxy_DefineElement(JSContext *cx, HandleObject obj, uint32_t index, HandleValue value,
PropertyOp getter, StrictPropertyOp setter, unsigned attrs)
{
RootedId id(cx);
if (!IndexToId(cx, index, &id))
return false;
return proxy_DefineGeneric(cx, obj, id, value, getter, setter, attrs);
}
bool
js::proxy_GetGeneric(JSContext *cx, HandleObject obj, HandleObject receiver, HandleId id,
MutableHandleValue vp)
{
return Proxy::get(cx, obj, receiver, id, vp);
}
bool
js::proxy_GetProperty(JSContext *cx, HandleObject obj, HandleObject receiver, HandlePropertyName name,
MutableHandleValue vp)
{
Rooted<jsid> id(cx, NameToId(name));
return proxy_GetGeneric(cx, obj, receiver, id, vp);
}
bool
js::proxy_GetElement(JSContext *cx, HandleObject obj, HandleObject receiver, uint32_t index,
MutableHandleValue vp)
{
RootedId id(cx);
if (!IndexToId(cx, index, &id))
return false;
return proxy_GetGeneric(cx, obj, receiver, id, vp);
}
bool
js::proxy_SetGeneric(JSContext *cx, HandleObject obj, HandleId id,
MutableHandleValue vp, bool strict)
{
return Proxy::set(cx, obj, obj, id, strict, vp);
}
bool
js::proxy_SetProperty(JSContext *cx, HandleObject obj, HandlePropertyName name,
MutableHandleValue vp, bool strict)
{
Rooted<jsid> id(cx, NameToId(name));
return proxy_SetGeneric(cx, obj, id, vp, strict);
}
bool
js::proxy_SetElement(JSContext *cx, HandleObject obj, uint32_t index,
MutableHandleValue vp, bool strict)
{
RootedId id(cx);
if (!IndexToId(cx, index, &id))
return false;
return proxy_SetGeneric(cx, obj, id, vp, strict);
}
bool
js::proxy_GetGenericAttributes(JSContext *cx, HandleObject obj, HandleId id, unsigned *attrsp)
{
Rooted<PropertyDescriptor> desc(cx);
if (!Proxy::getOwnPropertyDescriptor(cx, obj, id, &desc))
return false;
*attrsp = desc.attributes();
return true;
}
bool
js::proxy_SetGenericAttributes(JSContext *cx, HandleObject obj, HandleId id, unsigned *attrsp)
{
/* Lookup the current property descriptor so we have setter/getter/value. */
Rooted<PropertyDescriptor> desc(cx);
if (!Proxy::getOwnPropertyDescriptor(cx, obj, id, &desc))
return false;
desc.setAttributes(*attrsp);
return Proxy::defineProperty(cx, obj, id, &desc);
}
bool
js::proxy_DeleteGeneric(JSContext *cx, HandleObject obj, HandleId id, bool *succeeded)
{
bool deleted;
if (!Proxy::delete_(cx, obj, id, &deleted))
return false;
*succeeded = deleted;
return js_SuppressDeletedProperty(cx, obj, id);
}
void
js::proxy_Trace(JSTracer *trc, JSObject *obj)
{
JS_ASSERT(obj->is<ProxyObject>());
ProxyObject::trace(trc, obj);
}
/* static */ void
ProxyObject::trace(JSTracer *trc, JSObject *obj)
{
ProxyObject *proxy = &obj->as<ProxyObject>();
#ifdef DEBUG
if (!trc->runtime()->gc.disableStrictProxyCheckingCount && proxy->is<WrapperObject>()) {
JSObject *referent = &proxy->private_().toObject();
if (referent->compartment() != proxy->compartment()) {
/*
* Assert that this proxy is tracked in the wrapper map. We maintain
* the invariant that the wrapped object is the key in the wrapper map.
*/
Value key = ObjectValue(*referent);
WrapperMap::Ptr p = proxy->compartment()->lookupWrapper(key);
JS_ASSERT(*p->value().unsafeGet() == ObjectValue(*proxy));
}
}
#endif
// Note: If you add new slots here, make sure to change
// nuke() to cope.
MarkCrossCompartmentSlot(trc, obj, proxy->slotOfPrivate(), "private");
MarkSlot(trc, proxy->slotOfExtra(0), "extra0");
/*
* The GC can use the second reserved slot to link the cross compartment
* wrappers into a linked list, in which case we don't want to trace it.
*/
if (!proxy->is<CrossCompartmentWrapperObject>())
MarkSlot(trc, proxy->slotOfExtra(1), "extra1");
/*
* Allow for people to add extra slots to "proxy" classes, without allowing
* them to set their own trace hook. Trace the extras.
*/
unsigned numSlots = JSCLASS_RESERVED_SLOTS(proxy->getClass());
for (unsigned i = PROXY_MINIMUM_SLOTS; i < numSlots; i++)
MarkSlot(trc, proxy->slotOfClassSpecific(i), "class-specific");
}
JSObject *
js::proxy_WeakmapKeyDelegate(JSObject *obj)
{
JS_ASSERT(obj->is<ProxyObject>());
return obj->as<ProxyObject>().handler()->weakmapKeyDelegate(obj);
}
bool
js::proxy_Convert(JSContext *cx, HandleObject proxy, JSType hint, MutableHandleValue vp)
{
JS_ASSERT(proxy->is<ProxyObject>());
return Proxy::defaultValue(cx, proxy, hint, vp);
}
void
js::proxy_Finalize(FreeOp *fop, JSObject *obj)
{
JS_ASSERT(obj->is<ProxyObject>());
obj->as<ProxyObject>().handler()->finalize(fop, obj);
}
bool
js::proxy_HasInstance(JSContext *cx, HandleObject proxy, MutableHandleValue v, bool *bp)
{
bool b;
if (!Proxy::hasInstance(cx, proxy, v, &b))
return false;
*bp = !!b;
return true;
}
bool
js::proxy_Call(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject proxy(cx, &args.callee());
JS_ASSERT(proxy->is<ProxyObject>());
return Proxy::call(cx, proxy, args);
}
bool
js::proxy_Construct(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject proxy(cx, &args.callee());
JS_ASSERT(proxy->is<ProxyObject>());
return Proxy::construct(cx, proxy, args);
}
bool
js::proxy_Watch(JSContext *cx, HandleObject obj, HandleId id, HandleObject callable)
{
return Proxy::watch(cx, obj, id, callable);
}
bool
js::proxy_Unwatch(JSContext *cx, HandleObject obj, HandleId id)
{
return Proxy::unwatch(cx, obj, id);
}
bool
js::proxy_Slice(JSContext *cx, HandleObject proxy, uint32_t begin, uint32_t end,
HandleObject result)
{
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::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_;
JS_FRIEND_API(JSObject *)
js::NewProxyObject(JSContext *cx, BaseProxyHandler *handler, HandleValue priv, JSObject *proto_,
JSObject *parent_, const ProxyOptions &options)
{
return ProxyObject::New(cx, handler, priv, TaggedProto(proto_), parent_,
options);
}
void
ProxyObject::renew(JSContext *cx, BaseProxyHandler *handler, Value priv)
{
JS_ASSERT_IF(IsCrossCompartmentWrapper(this), IsDeadProxyObject(this));
JS_ASSERT(getParent() == cx->global());
JS_ASSERT(getClass() == &uncallableClass_);
JS_ASSERT(!getClass()->ext.innerObject);
JS_ASSERT(getTaggedProto().isLazy());
setSlot(HANDLER_SLOT, PrivateValue(handler));
setCrossCompartmentSlot(PRIVATE_SLOT, priv);
setSlot(EXTRA_SLOT + 0, UndefinedValue());
setSlot(EXTRA_SLOT + 1, UndefinedValue());
}
static bool
proxy(JSContext *cx, unsigned argc, jsval *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() < 2) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
"Proxy", "1", "s");
return false;
}
RootedObject target(cx, NonNullObject(cx, args[0]));
if (!target)
return false;
RootedObject handler(cx, NonNullObject(cx, args[1]));
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);
if (!proxy)
return false;
proxy->setExtra(0, ObjectOrNullValue(handler));
args.rval().setObject(*proxy);
return true;
}
static bool
proxy_create(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() < 1) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
"create", "0", "s");
return false;
}
JSObject *handler = NonNullObject(cx, args[0]);
if (!handler)
return false;
JSObject *proto, *parent = nullptr;
if (args.get(1).isObject()) {
proto = &args[1].toObject();
parent = proto->getParent();
} else {
JS_ASSERT(IsFunctionObject(&args.callee()));
proto = nullptr;
}
if (!parent)
parent = args.callee().getParent();
RootedValue priv(cx, ObjectValue(*handler));
JSObject *proxy = NewProxyObject(cx, &ScriptedIndirectProxyHandler::singleton,
priv, proto, parent);
if (!proxy)
return false;
args.rval().setObject(*proxy);
return true;
}
static bool
proxy_createFunction(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() < 2) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
"createFunction", "1", "");
return false;
}
RootedObject handler(cx, NonNullObject(cx, args[0]));
if (!handler)
return false;
RootedObject proto(cx), parent(cx);
parent = args.callee().getParent();
proto = parent->global().getOrCreateFunctionPrototype(cx);
if (!proto)
return false;
parent = proto->getParent();
RootedObject call(cx, ValueToCallable(cx, args[1], args.length() - 2));
if (!call)
return false;
RootedObject construct(cx, nullptr);
if (args.length() > 2) {
construct = ValueToCallable(cx, args[2], args.length() - 3);
if (!construct)
return false;
} else {
construct = call;
}
// Stash the call and construct traps on a holder object that we can stick
// in a slot on the proxy.
RootedObject ccHolder(cx, JS_NewObjectWithGivenProto(cx, Jsvalify(&CallConstructHolder),
js::NullPtr(), cx->global()));
if (!ccHolder)
return false;
ccHolder->setReservedSlot(0, ObjectValue(*call));
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);
if (!proxy)
return false;
proxy->as<ProxyObject>().setExtra(0, ObjectValue(*ccHolder));
args.rval().setObject(*proxy);
return true;
}
JS_FRIEND_API(JSObject *)
js_InitProxyClass(JSContext *cx, HandleObject obj)
{
static const JSFunctionSpec static_methods[] = {
JS_FN("create", proxy_create, 2, 0),
JS_FN("createFunction", proxy_createFunction, 3, 0),
JS_FS_END
};
Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
RootedFunction ctor(cx);
ctor = global->createConstructor(cx, proxy, cx->names().Proxy, 2);
if (!ctor)
return nullptr;
if (!JS_DefineFunctions(cx, ctor, static_methods))
return nullptr;
if (!JS_DefineProperty(cx, obj, "Proxy", ctor, 0,
JS_PropertyStub, JS_StrictPropertyStub)) {
return nullptr;
}
global->setConstructor(JSProto_Proxy, ObjectValue(*ctor));
return ctor;
}