blob: f19338a927373e79c90b9f019badf38500f1c39b [file] [log] [blame]
/* -*- 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 "js/Proxy.h"
#include "vm/ProxyObject.h"
#include "jscntxtinlines.h"
#include "jsobjinlines.h"
using namespace js;
using JS::IsArrayAnswer;
bool
BaseProxyHandler::enter(JSContext* cx, HandleObject wrapper, HandleId id, Action act,
bool* bp) const
{
*bp = true;
return true;
}
bool
BaseProxyHandler::has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const
{
assertEnteredPolicy(cx, proxy, id, GET);
Rooted<PropertyDescriptor> desc(cx);
if (!getPropertyDescriptor(cx, proxy, id, &desc))
return false;
*bp = !!desc.object();
return true;
}
bool
BaseProxyHandler::getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
MutableHandle<PropertyDescriptor> desc) const
{
assertEnteredPolicy(cx, proxy, id, GET | SET | GET_PROPERTY_DESCRIPTOR);
if (!getOwnPropertyDescriptor(cx, proxy, id, desc))
return false;
if (desc.object())
return true;
RootedObject proto(cx);
if (!GetPrototype(cx, proxy, &proto))
return false;
if (!proto) {
MOZ_ASSERT(!desc.object());
return true;
}
return GetPropertyDescriptor(cx, proto, id, desc);
}
bool
BaseProxyHandler::hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const
{
assertEnteredPolicy(cx, proxy, id, GET);
Rooted<PropertyDescriptor> desc(cx);
if (!getOwnPropertyDescriptor(cx, proxy, id, &desc))
return false;
*bp = !!desc.object();
return true;
}
bool
BaseProxyHandler::get(JSContext* cx, HandleObject proxy, HandleValue receiver,
HandleId id, MutableHandleValue vp) const
{
assertEnteredPolicy(cx, proxy, id, GET);
Rooted<PropertyDescriptor> desc(cx);
if (!getPropertyDescriptor(cx, proxy, id, &desc))
return false;
if (!desc.object()) {
vp.setUndefined();
return true;
}
desc.assertComplete();
MOZ_ASSERT(desc.getter() != JS_PropertyStub);
if (!desc.getter()) {
vp.set(desc.value());
return true;
}
if (desc.hasGetterObject())
return InvokeGetter(cx, receiver, ObjectValue(*desc.getterObject()), vp);
if (!desc.isShared())
vp.set(desc.value());
else
vp.setUndefined();
// A proxy object should never have own JSGetterOps.
MOZ_ASSERT(desc.object() != proxy);
return CallJSGetterOp(cx, desc.getter(), desc.object(), id, vp);
}
bool
BaseProxyHandler::set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v,
HandleValue receiver, ObjectOpResult& result) const
{
assertEnteredPolicy(cx, proxy, id, SET);
// This method is not covered by any spec, but we follow ES6 draft rev 28
// (2014 Oct 14) 9.1.9 fairly closely, adapting it slightly for
// SpiderMonkey's particular foibles.
// Steps 2-3. (Step 1 is a superfluous assertion.)
Rooted<PropertyDescriptor> ownDesc(cx);
if (!getOwnPropertyDescriptor(cx, proxy, id, &ownDesc))
return false;
ownDesc.assertCompleteIfFound();
// The rest is factored out into a separate function with a weird name.
// This algorithm continues just below.
return SetPropertyIgnoringNamedGetter(cx, proxy, id, v, receiver, ownDesc, result);
}
bool
js::SetPropertyIgnoringNamedGetter(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
HandleValue receiver, Handle<PropertyDescriptor> ownDesc_,
ObjectOpResult& result)
{
Rooted<PropertyDescriptor> ownDesc(cx, ownDesc_);
// Step 4.
if (!ownDesc.object()) {
// The spec calls this variable "parent", but that word has weird
// connotations in SpiderMonkey, so let's go with "proto".
RootedObject proto(cx);
if (!GetPrototype(cx, obj, &proto))
return false;
if (proto)
return SetProperty(cx, proto, id, v, receiver, result);
// Step 4.d.
ownDesc.setDataDescriptor(UndefinedHandleValue, JSPROP_ENUMERATE);
}
// Step 5.
if (ownDesc.isDataDescriptor()) {
// Steps 5.a-b.
if (!ownDesc.writable())
return result.fail(JSMSG_READ_ONLY);
if (!receiver.isObject())
return result.fail(JSMSG_SET_NON_OBJECT_RECEIVER);
RootedObject receiverObj(cx, &receiver.toObject());
// Nonstandard SpiderMonkey special case: setter ops.
SetterOp setter = ownDesc.setter();
MOZ_ASSERT(setter != JS_StrictPropertyStub);
if (setter && setter != JS_StrictPropertyStub) {
RootedValue valCopy(cx, v);
return CallJSSetterOp(cx, setter, receiverObj, id, &valCopy, result);
}
// Steps 5.c-d.
Rooted<PropertyDescriptor> existingDescriptor(cx);
if (!GetOwnPropertyDescriptor(cx, receiverObj, id, &existingDescriptor))
return false;
// Step 5.e.
if (existingDescriptor.object()) {
// Step 5.e.i.
if (existingDescriptor.isAccessorDescriptor())
return result.fail(JSMSG_OVERWRITING_ACCESSOR);
// Step 5.e.ii.
if (!existingDescriptor.writable())
return result.fail(JSMSG_READ_ONLY);
}
// Steps 5.e.iii-iv. and 5.f.i.
unsigned attrs =
existingDescriptor.object()
? JSPROP_IGNORE_ENUMERATE | JSPROP_IGNORE_READONLY | JSPROP_IGNORE_PERMANENT
: JSPROP_ENUMERATE;
// A very old nonstandard SpiderMonkey extension: default to the Class
// getter and setter ops.
const Class* clasp = receiverObj->getClass();
MOZ_ASSERT(clasp->getProperty != JS_PropertyStub);
MOZ_ASSERT(clasp->setProperty != JS_StrictPropertyStub);
return DefineProperty(cx, receiverObj, id, v, clasp->getProperty, clasp->setProperty,
attrs, result);
}
// Step 6.
MOZ_ASSERT(ownDesc.isAccessorDescriptor());
RootedObject setter(cx);
if (ownDesc.hasSetterObject())
setter = ownDesc.setterObject();
if (!setter)
return result.fail(JSMSG_GETTER_ONLY);
RootedValue setterValue(cx, ObjectValue(*setter));
if (!InvokeSetter(cx, receiver, setterValue, v))
return false;
return result.succeed();
}
bool
BaseProxyHandler::getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy,
AutoIdVector& props) const
{
assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE);
MOZ_ASSERT(props.length() == 0);
if (!ownPropertyKeys(cx, proxy, props))
return false;
/* Select only the enumerable properties through in-place iteration. */
RootedId id(cx);
size_t i = 0;
for (size_t j = 0, len = props.length(); j < len; j++) {
MOZ_ASSERT(i <= j);
id = props[j];
if (JSID_IS_SYMBOL(id))
continue;
AutoWaivePolicy policy(cx, proxy, id, BaseProxyHandler::GET);
Rooted<PropertyDescriptor> desc(cx);
if (!getOwnPropertyDescriptor(cx, proxy, id, &desc))
return false;
desc.assertCompleteIfFound();
if (desc.object() && desc.enumerable())
props[i++].set(id);
}
MOZ_ASSERT(i <= props.length());
props.resize(i);
return true;
}
bool
BaseProxyHandler::enumerate(JSContext* cx, HandleObject proxy, MutableHandleObject objp) const
{
assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE);
// GetPropertyKeys will invoke getOwnEnumerablePropertyKeys along the proto
// chain for us.
AutoIdVector props(cx);
if (!GetPropertyKeys(cx, proxy, 0, &props))
return false;
return EnumeratedIdVectorToIterator(cx, proxy, 0, props, objp);
}
bool
BaseProxyHandler::call(JSContext* cx, HandleObject proxy, const CallArgs& args) const
{
MOZ_CRASH("callable proxies should implement call trap");
}
bool
BaseProxyHandler::construct(JSContext* cx, HandleObject proxy, const CallArgs& args) const
{
MOZ_CRASH("callable proxies should implement construct trap");
}
const char*
BaseProxyHandler::className(JSContext* cx, HandleObject proxy) const
{
return proxy->isCallable() ? "Function" : "Object";
}
JSString*
BaseProxyHandler::fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const
{
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) const
{
MOZ_CRASH("This should have been a wrapped regexp");
}
bool
BaseProxyHandler::boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const
{
vp.setUndefined();
return true;
}
bool
BaseProxyHandler::nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
const CallArgs& args) const
{
ReportIncompatible(cx, args);
return false;
}
bool
BaseProxyHandler::hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v,
bool* bp) const
{
assertEnteredPolicy(cx, proxy, JSID_VOID, GET);
RootedValue val(cx, ObjectValue(*proxy.get()));
ReportValueError(cx, JSMSG_BAD_INSTANCEOF_RHS,
JSDVG_SEARCH_STACK, val, nullptr);
return false;
}
bool
BaseProxyHandler::getBuiltinClass(JSContext* cx, HandleObject proxy,
ESClassValue* classValue) const
{
*classValue = ESClass_Other;
return true;
}
bool
BaseProxyHandler::isArray(JSContext* cx, HandleObject proxy, IsArrayAnswer* answer) const
{
*answer = IsArrayAnswer::NotArray;
return true;
}
void
BaseProxyHandler::trace(JSTracer* trc, JSObject* proxy) const
{
}
void
BaseProxyHandler::finalize(JSFreeOp* fop, JSObject* proxy) const
{
}
void
BaseProxyHandler::objectMoved(JSObject* proxy, const JSObject* old) const
{
}
JSObject*
BaseProxyHandler::weakmapKeyDelegate(JSObject* proxy) const
{
return nullptr;
}
bool
BaseProxyHandler::getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject protop) const
{
MOZ_CRASH("Must override getPrototype with lazy prototype.");
}
bool
BaseProxyHandler::setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto,
ObjectOpResult& result) const
{
// 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, GetErrorMessage, nullptr, JSMSG_CANT_SET_PROTO_OF,
"incompatible Proxy");
return false;
}
bool
BaseProxyHandler::setImmutablePrototype(JSContext* cx, HandleObject proxy, bool* succeeded) const
{
*succeeded = false;
return true;
}
bool
BaseProxyHandler::watch(JSContext* cx, HandleObject proxy, HandleId id, HandleObject callable) const
{
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_CANT_WATCH,
proxy->getClass()->name);
return false;
}
bool
BaseProxyHandler::unwatch(JSContext* cx, HandleObject proxy, HandleId id) const
{
return true;
}
bool
BaseProxyHandler::getElements(JSContext* cx, HandleObject proxy, uint32_t begin, uint32_t end,
ElementAdder* adder) const
{
assertEnteredPolicy(cx, proxy, JSID_VOID, GET);
return js::GetElementsWithAdder(cx, proxy, proxy, begin, end, adder);
}
bool
BaseProxyHandler::isCallable(JSObject* obj) const
{
return false;
}
bool
BaseProxyHandler::isConstructor(JSObject* obj) const
{
return false;
}