785 lines
25 KiB
C++
785 lines
25 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
* 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 "js/Proxy.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include "jsapi.h"
|
|
#include "jscntxt.h"
|
|
#include "jsfun.h"
|
|
#include "jsgc.h"
|
|
#include "jswrapper.h"
|
|
|
|
#include "gc/Marking.h"
|
|
#include "proxy/DeadObjectProxy.h"
|
|
#include "proxy/ScriptedProxyHandler.h"
|
|
#include "vm/WrapperObject.h"
|
|
|
|
#include "jsatominlines.h"
|
|
#include "jsobjinlines.h"
|
|
|
|
#include "vm/NativeObject-inl.h"
|
|
|
|
using namespace js;
|
|
using namespace js::gc;
|
|
|
|
void
|
|
js::AutoEnterPolicy::reportErrorIfExceptionIsNotPending(JSContext* cx, jsid id)
|
|
{
|
|
if (JS_IsExceptionPending(cx))
|
|
return;
|
|
|
|
if (JSID_IS_VOID(id)) {
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_OBJECT_ACCESS_DENIED);
|
|
} else {
|
|
RootedValue idVal(cx, IdToValue(id));
|
|
JSString* str = ValueToSource(cx, idVal);
|
|
if (!str) {
|
|
return;
|
|
}
|
|
AutoStableStringChars chars(cx);
|
|
const char16_t* prop = nullptr;
|
|
if (str->ensureFlat(cx) && chars.initTwoByte(cx, str))
|
|
prop = chars.twoByteChars();
|
|
|
|
JS_ReportErrorNumberUC(cx, 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.emplace(proxy);
|
|
enteredId.emplace(id);
|
|
enteredAction = act;
|
|
prev = cx->runtime()->enteredPolicy;
|
|
cx->runtime()->enteredPolicy = this;
|
|
}
|
|
}
|
|
|
|
void
|
|
js::AutoEnterPolicy::recordLeave()
|
|
{
|
|
if (enteredProxy) {
|
|
MOZ_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->get() == proxy);
|
|
MOZ_ASSERT(cx->runtime()->enteredPolicy->enteredId->get() == id);
|
|
MOZ_ASSERT(cx->runtime()->enteredPolicy->enteredAction & act);
|
|
}
|
|
#endif
|
|
|
|
bool
|
|
Proxy::getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
|
|
MutableHandle<PropertyDescriptor> desc)
|
|
{
|
|
JS_CHECK_RECURSION(cx, return false);
|
|
const 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_PROPERTY_DESCRIPTOR, true);
|
|
if (!policy.allowed())
|
|
return policy.returnValue();
|
|
|
|
// Special case. See the comment on BaseProxyHandler::mHasPrototype.
|
|
if (handler->hasPrototype())
|
|
return handler->BaseProxyHandler::getPropertyDescriptor(cx, proxy, id, desc);
|
|
|
|
return handler->getPropertyDescriptor(cx, proxy, id, desc);
|
|
}
|
|
|
|
bool
|
|
Proxy::getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
|
|
MutableHandle<PropertyDescriptor> desc)
|
|
{
|
|
JS_CHECK_RECURSION(cx, return false);
|
|
|
|
const 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_PROPERTY_DESCRIPTOR, true);
|
|
if (!policy.allowed())
|
|
return policy.returnValue();
|
|
return handler->getOwnPropertyDescriptor(cx, proxy, id, desc);
|
|
}
|
|
|
|
bool
|
|
Proxy::defineProperty(JSContext* cx, HandleObject proxy, HandleId id,
|
|
Handle<PropertyDescriptor> desc, ObjectOpResult& result)
|
|
{
|
|
JS_CHECK_RECURSION(cx, return false);
|
|
const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
|
|
AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true);
|
|
if (!policy.allowed()) {
|
|
if (!policy.returnValue())
|
|
return false;
|
|
return result.succeed();
|
|
}
|
|
return proxy->as<ProxyObject>().handler()->defineProperty(cx, proxy, id, desc, result);
|
|
}
|
|
|
|
bool
|
|
Proxy::ownPropertyKeys(JSContext* cx, HandleObject proxy, AutoIdVector& props)
|
|
{
|
|
JS_CHECK_RECURSION(cx, return false);
|
|
const 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()->ownPropertyKeys(cx, proxy, props);
|
|
}
|
|
|
|
bool
|
|
Proxy::delete_(JSContext* cx, HandleObject proxy, HandleId id, ObjectOpResult& result)
|
|
{
|
|
JS_CHECK_RECURSION(cx, return false);
|
|
const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
|
|
AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true);
|
|
if (!policy.allowed()) {
|
|
bool ok = policy.returnValue();
|
|
if (ok)
|
|
result.succeed();
|
|
return ok;
|
|
}
|
|
return proxy->as<ProxyObject>().handler()->delete_(cx, proxy, id, result);
|
|
}
|
|
|
|
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) {
|
|
if (!uniqueOthers.append(others[i]))
|
|
return false;
|
|
}
|
|
}
|
|
return base.appendAll(uniqueOthers);
|
|
}
|
|
|
|
/* static */ bool
|
|
Proxy::getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject proto)
|
|
{
|
|
MOZ_ASSERT(proxy->hasDynamicPrototype());
|
|
JS_CHECK_RECURSION(cx, return false);
|
|
return proxy->as<ProxyObject>().handler()->getPrototype(cx, proxy, proto);
|
|
}
|
|
|
|
/* static */ bool
|
|
Proxy::setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto, ObjectOpResult& result)
|
|
{
|
|
MOZ_ASSERT(proxy->hasDynamicPrototype());
|
|
JS_CHECK_RECURSION(cx, return false);
|
|
return proxy->as<ProxyObject>().handler()->setPrototype(cx, proxy, proto, result);
|
|
}
|
|
|
|
/* static */ bool
|
|
Proxy::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
|
|
MutableHandleObject proto)
|
|
{
|
|
JS_CHECK_RECURSION(cx, return false);
|
|
return proxy->as<ProxyObject>().handler()->getPrototypeIfOrdinary(cx, proxy, isOrdinary,
|
|
proto);
|
|
}
|
|
|
|
/* static */ bool
|
|
Proxy::setImmutablePrototype(JSContext* cx, HandleObject proxy, bool* succeeded)
|
|
{
|
|
JS_CHECK_RECURSION(cx, return false);
|
|
const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
|
|
return handler->setImmutablePrototype(cx, proxy, succeeded);
|
|
}
|
|
|
|
/* static */ bool
|
|
Proxy::preventExtensions(JSContext* cx, HandleObject proxy, ObjectOpResult& result)
|
|
{
|
|
JS_CHECK_RECURSION(cx, return false);
|
|
const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
|
|
return handler->preventExtensions(cx, proxy, result);
|
|
}
|
|
|
|
/* static */ 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::has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp)
|
|
{
|
|
JS_CHECK_RECURSION(cx, return false);
|
|
const 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()) {
|
|
if (!handler->hasOwn(cx, proxy, id, bp))
|
|
return false;
|
|
if (*bp)
|
|
return true;
|
|
|
|
RootedObject proto(cx);
|
|
if (!GetPrototype(cx, proxy, &proto))
|
|
return false;
|
|
if (!proto)
|
|
return true;
|
|
|
|
return HasProperty(cx, proto, id, bp);
|
|
}
|
|
|
|
return handler->has(cx, proxy, id, bp);
|
|
}
|
|
|
|
bool
|
|
Proxy::hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp)
|
|
{
|
|
JS_CHECK_RECURSION(cx, return false);
|
|
const 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);
|
|
}
|
|
|
|
static Value
|
|
ValueToWindowProxyIfWindow(const Value& v)
|
|
{
|
|
if (v.isObject())
|
|
return ObjectValue(*ToWindowProxyIfWindow(&v.toObject()));
|
|
return v;
|
|
}
|
|
|
|
bool
|
|
Proxy::get(JSContext* cx, HandleObject proxy, HandleValue receiver_, HandleId id,
|
|
MutableHandleValue vp)
|
|
{
|
|
JS_CHECK_RECURSION(cx, return false);
|
|
const 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();
|
|
|
|
// Use the WindowProxy as receiver if receiver_ is a Window. Proxy handlers
|
|
// shouldn't have to know about the Window/WindowProxy distinction.
|
|
RootedValue receiver(cx, ValueToWindowProxyIfWindow(receiver_));
|
|
|
|
if (handler->hasPrototype()) {
|
|
bool own;
|
|
if (!handler->hasOwn(cx, proxy, id, &own))
|
|
return false;
|
|
if (!own) {
|
|
RootedObject proto(cx);
|
|
if (!GetPrototype(cx, proxy, &proto))
|
|
return false;
|
|
if (!proto)
|
|
return true;
|
|
return GetProperty(cx, proto, receiver, id, vp);
|
|
}
|
|
}
|
|
|
|
return handler->get(cx, proxy, receiver, id, vp);
|
|
}
|
|
|
|
bool
|
|
Proxy::set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, HandleValue receiver_,
|
|
ObjectOpResult& result)
|
|
{
|
|
JS_CHECK_RECURSION(cx, return false);
|
|
const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
|
|
AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true);
|
|
if (!policy.allowed()) {
|
|
if (!policy.returnValue())
|
|
return false;
|
|
return result.succeed();
|
|
}
|
|
|
|
// Use the WindowProxy as receiver if receiver_ is a Window. Proxy handlers
|
|
// shouldn't have to know about the Window/WindowProxy distinction.
|
|
RootedValue receiver(cx, ValueToWindowProxyIfWindow(receiver_));
|
|
|
|
// Special case. See the comment on BaseProxyHandler::mHasPrototype.
|
|
if (handler->hasPrototype())
|
|
return handler->BaseProxyHandler::set(cx, proxy, id, v, receiver, result);
|
|
|
|
return handler->set(cx, proxy, id, v, receiver, result);
|
|
}
|
|
|
|
bool
|
|
Proxy::getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy, AutoIdVector& props)
|
|
{
|
|
JS_CHECK_RECURSION(cx, return false);
|
|
const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
|
|
AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::ENUMERATE, true);
|
|
if (!policy.allowed())
|
|
return policy.returnValue();
|
|
return handler->getOwnEnumerablePropertyKeys(cx, proxy, props);
|
|
}
|
|
|
|
bool
|
|
Proxy::enumerate(JSContext* cx, HandleObject proxy, MutableHandleObject objp)
|
|
{
|
|
JS_CHECK_RECURSION(cx, return false);
|
|
const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
|
|
objp.set(nullptr); // default result if we refuse to perform this action
|
|
|
|
if (handler->hasPrototype()) {
|
|
AutoIdVector props(cx);
|
|
if (!Proxy::getOwnEnumerablePropertyKeys(cx, proxy, props))
|
|
return false;
|
|
|
|
RootedObject proto(cx);
|
|
if (!GetPrototype(cx, proxy, &proto))
|
|
return false;
|
|
if (!proto)
|
|
return EnumeratedIdVectorToIterator(cx, proxy, 0, props, objp);
|
|
assertSameCompartment(cx, proxy, proto);
|
|
|
|
AutoIdVector protoProps(cx);
|
|
return GetPropertyKeys(cx, proto, 0, &protoProps) &&
|
|
AppendUnique(cx, props, protoProps) &&
|
|
EnumeratedIdVectorToIterator(cx, proxy, 0, props, objp);
|
|
}
|
|
|
|
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()) {
|
|
return policy.returnValue() &&
|
|
NewEmptyPropertyIterator(cx, 0, objp);
|
|
}
|
|
return handler->enumerate(cx, proxy, objp);
|
|
}
|
|
|
|
bool
|
|
Proxy::call(JSContext* cx, HandleObject proxy, const CallArgs& args)
|
|
{
|
|
JS_CHECK_RECURSION(cx, return false);
|
|
const 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);
|
|
const 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, const 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);
|
|
const 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::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls)
|
|
{
|
|
JS_CHECK_RECURSION(cx, return false);
|
|
return proxy->as<ProxyObject>().handler()->getBuiltinClass(cx, proxy, cls);
|
|
}
|
|
|
|
bool
|
|
Proxy::isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer)
|
|
{
|
|
JS_CHECK_RECURSION(cx, return false);
|
|
return proxy->as<ProxyObject>().handler()->isArray(cx, proxy, answer);
|
|
}
|
|
|
|
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";
|
|
|
|
const 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);
|
|
const 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::boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp)
|
|
{
|
|
JS_CHECK_RECURSION(cx, return false);
|
|
return proxy->as<ProxyObject>().handler()->boxedValue_unbox(cx, proxy, vp);
|
|
}
|
|
|
|
JSObject * const TaggedProto::LazyProto = reinterpret_cast<JSObject*>(0x1);
|
|
|
|
/* static */ bool
|
|
Proxy::getElements(JSContext* cx, HandleObject proxy, uint32_t begin, uint32_t end,
|
|
ElementAdder* adder)
|
|
{
|
|
JS_CHECK_RECURSION(cx, return false);
|
|
const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
|
|
AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::GET,
|
|
/* mayThrow = */ true);
|
|
if (!policy.allowed()) {
|
|
if (policy.returnValue()) {
|
|
MOZ_ASSERT(!cx->isExceptionPending());
|
|
return js::GetElementsWithAdder(cx, proxy, proxy, begin, end, adder);
|
|
}
|
|
return false;
|
|
}
|
|
return handler->getElements(cx, proxy, begin, end, adder);
|
|
}
|
|
|
|
/* static */ void
|
|
Proxy::trace(JSTracer* trc, JSObject* proxy)
|
|
{
|
|
const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
|
|
handler->trace(trc, proxy);
|
|
}
|
|
|
|
bool
|
|
js::proxy_LookupProperty(JSContext* cx, HandleObject obj, HandleId id,
|
|
MutableHandleObject objp, MutableHandleShape propp)
|
|
{
|
|
bool found;
|
|
if (!Proxy::has(cx, obj, id, &found))
|
|
return false;
|
|
|
|
if (found) {
|
|
MarkNonNativePropertyFound<CanGC>(propp);
|
|
objp.set(obj);
|
|
} else {
|
|
objp.set(nullptr);
|
|
propp.set(nullptr);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
js::proxy_DefineProperty(JSContext* cx, HandleObject obj, HandleId id,
|
|
Handle<PropertyDescriptor> desc,
|
|
ObjectOpResult& result)
|
|
{
|
|
return Proxy::defineProperty(cx, obj, id, desc, result);
|
|
}
|
|
|
|
bool
|
|
js::proxy_HasProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* foundp)
|
|
{
|
|
return Proxy::has(cx, obj, id, foundp);
|
|
}
|
|
|
|
bool
|
|
js::proxy_GetProperty(JSContext* cx, HandleObject obj, HandleValue receiver, HandleId id,
|
|
MutableHandleValue vp)
|
|
{
|
|
return Proxy::get(cx, obj, receiver, id, vp);
|
|
}
|
|
|
|
bool
|
|
js::proxy_SetProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
|
|
HandleValue receiver, ObjectOpResult& result)
|
|
{
|
|
return Proxy::set(cx, obj, id, v, receiver, result);
|
|
}
|
|
|
|
bool
|
|
js::proxy_GetOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id,
|
|
MutableHandle<PropertyDescriptor> desc)
|
|
{
|
|
return Proxy::getOwnPropertyDescriptor(cx, obj, id, desc);
|
|
}
|
|
|
|
bool
|
|
js::proxy_DeleteProperty(JSContext* cx, HandleObject obj, HandleId id, ObjectOpResult& result)
|
|
{
|
|
if (!Proxy::delete_(cx, obj, id, result))
|
|
return false;
|
|
return SuppressDeletedProperty(cx, obj, id); // XXX is this necessary?
|
|
}
|
|
|
|
void
|
|
js::proxy_Trace(JSTracer* trc, JSObject* obj)
|
|
{
|
|
MOZ_ASSERT(obj->is<ProxyObject>());
|
|
ProxyObject::trace(trc, obj);
|
|
}
|
|
|
|
/* static */ void
|
|
ProxyObject::trace(JSTracer* trc, JSObject* obj)
|
|
{
|
|
ProxyObject* proxy = &obj->as<ProxyObject>();
|
|
|
|
TraceEdge(trc, &proxy->shape_, "ProxyObject_shape");
|
|
|
|
#ifdef DEBUG
|
|
if (trc->runtime()->gc.isStrictProxyCheckingEnabled() && proxy->is<WrapperObject>()) {
|
|
JSObject* referent = MaybeForwarded(proxy->target());
|
|
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);
|
|
MOZ_ASSERT(p);
|
|
MOZ_ASSERT(*p->value().unsafeGet() == ObjectValue(*proxy));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Note: If you add new slots here, make sure to change
|
|
// nuke() to cope.
|
|
TraceCrossCompartmentEdge(trc, obj, proxy->slotOfPrivate(), "private");
|
|
TraceEdge(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>())
|
|
TraceEdge(trc, proxy->slotOfExtra(1), "extra1");
|
|
|
|
Proxy::trace(trc, obj);
|
|
}
|
|
|
|
JSObject*
|
|
js::proxy_WeakmapKeyDelegate(JSObject* obj)
|
|
{
|
|
MOZ_ASSERT(obj->is<ProxyObject>());
|
|
return obj->as<ProxyObject>().handler()->weakmapKeyDelegate(obj);
|
|
}
|
|
|
|
void
|
|
js::proxy_Finalize(FreeOp* fop, JSObject* obj)
|
|
{
|
|
// Suppress a bogus warning about finalize().
|
|
JS::AutoSuppressGCAnalysis nogc;
|
|
|
|
MOZ_ASSERT(obj->is<ProxyObject>());
|
|
obj->as<ProxyObject>().handler()->finalize(fop, obj);
|
|
js_free(detail::GetProxyDataLayout(obj)->values);
|
|
}
|
|
|
|
void
|
|
js::proxy_ObjectMoved(JSObject* obj, const JSObject* old)
|
|
{
|
|
MOZ_ASSERT(obj->is<ProxyObject>());
|
|
obj->as<ProxyObject>().handler()->objectMoved(obj, old);
|
|
}
|
|
|
|
bool
|
|
js::proxy_HasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v, bool* bp)
|
|
{
|
|
return Proxy::hasInstance(cx, proxy, v, bp);
|
|
}
|
|
|
|
bool
|
|
js::proxy_Call(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
RootedObject proxy(cx, &args.callee());
|
|
MOZ_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());
|
|
MOZ_ASSERT(proxy->is<ProxyObject>());
|
|
return Proxy::construct(cx, proxy, args);
|
|
}
|
|
|
|
bool
|
|
js::proxy_GetElements(JSContext* cx, HandleObject proxy, uint32_t begin, uint32_t end,
|
|
ElementAdder* adder)
|
|
{
|
|
return Proxy::getElements(cx, proxy, begin, end, adder);
|
|
}
|
|
|
|
JSString*
|
|
js::proxy_FunToString(JSContext* cx, HandleObject proxy, unsigned indent)
|
|
{
|
|
return Proxy::fun_toString(cx, proxy, indent);
|
|
}
|
|
|
|
const ClassOps js::ProxyClassOps = {
|
|
nullptr, /* addProperty */
|
|
nullptr, /* delProperty */
|
|
nullptr, /* getProperty */
|
|
nullptr, /* setProperty */
|
|
nullptr, /* enumerate */
|
|
nullptr, /* resolve */
|
|
nullptr, /* mayResolve */
|
|
js::proxy_Finalize, /* finalize */
|
|
nullptr, /* call */
|
|
js::proxy_HasInstance, /* hasInstance */
|
|
nullptr, /* construct */
|
|
js::proxy_Trace, /* trace */
|
|
};
|
|
|
|
const ClassExtension js::ProxyClassExtension = PROXY_MAKE_EXT(
|
|
js::proxy_ObjectMoved
|
|
);
|
|
|
|
const ObjectOps js::ProxyObjectOps = {
|
|
js::proxy_LookupProperty,
|
|
js::proxy_DefineProperty,
|
|
js::proxy_HasProperty,
|
|
js::proxy_GetProperty,
|
|
js::proxy_SetProperty,
|
|
js::proxy_GetOwnPropertyDescriptor,
|
|
js::proxy_DeleteProperty,
|
|
js::proxy_GetElements,
|
|
nullptr, /* enumerate */
|
|
js::proxy_FunToString
|
|
};
|
|
|
|
const Class js::ProxyObject::proxyClass =
|
|
PROXY_CLASS_DEF("Proxy", JSCLASS_HAS_CACHED_PROTO(JSProto_Proxy));
|
|
|
|
const Class* const js::ProxyClassPtr = &js::ProxyObject::proxyClass;
|
|
|
|
JS_FRIEND_API(JSObject*)
|
|
js::NewProxyObject(JSContext* cx, const BaseProxyHandler* handler, HandleValue priv, JSObject* proto_,
|
|
const ProxyOptions& options)
|
|
{
|
|
if (options.lazyProto()) {
|
|
MOZ_ASSERT(!proto_);
|
|
proto_ = TaggedProto::LazyProto;
|
|
}
|
|
|
|
return ProxyObject::New(cx, handler, priv, TaggedProto(proto_), options);
|
|
}
|
|
|
|
void
|
|
ProxyObject::renew(const BaseProxyHandler* handler, const Value& priv)
|
|
{
|
|
MOZ_ASSERT(!IsInsideNursery(this));
|
|
MOZ_ASSERT_IF(IsCrossCompartmentWrapper(this), IsDeadProxyObject(this));
|
|
MOZ_ASSERT(getClass() == &ProxyObject::proxyClass);
|
|
MOZ_ASSERT(!IsWindowProxy(this));
|
|
MOZ_ASSERT(hasDynamicPrototype());
|
|
|
|
setHandler(handler);
|
|
setCrossCompartmentPrivate(priv);
|
|
setExtra(0, UndefinedValue());
|
|
setExtra(1, UndefinedValue());
|
|
}
|
|
|
|
JS_FRIEND_API(JSObject*)
|
|
js::InitProxyClass(JSContext* cx, HandleObject obj)
|
|
{
|
|
static const JSFunctionSpec static_methods[] = {
|
|
JS_FN("revocable", proxy_revocable, 2, 0),
|
|
JS_FS_END
|
|
};
|
|
|
|
Handle<GlobalObject*> global = obj.as<GlobalObject>();
|
|
RootedFunction ctor(cx);
|
|
ctor = GlobalObject::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, JSPROP_RESOLVING, JS_STUBGETTER, JS_STUBSETTER))
|
|
return nullptr;
|
|
|
|
global->setConstructor(JSProto_Proxy, ObjectValue(*ctor));
|
|
return ctor;
|
|
}
|