/* -*- 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/. */ #ifndef js_Proxy_h #define js_Proxy_h #include "mozilla/Maybe.h" #include "jsfriendapi.h" #include "js/CallNonGenericMethod.h" #include "js/Class.h" namespace js { using JS::AutoIdVector; using JS::CallArgs; using JS::Handle; using JS::HandleId; using JS::HandleObject; using JS::HandleValue; using JS::IsAcceptableThis; using JS::MutableHandle; using JS::MutableHandleObject; using JS::MutableHandleValue; using JS::NativeImpl; using JS::ObjectOpResult; using JS::PrivateValue; using JS::PropertyDescriptor; using JS::Value; class RegExpGuard; class JS_FRIEND_API(Wrapper); /* * A proxy is a JSObject with highly customizable behavior. ES6 specifies a * single kind of proxy, but the customization mechanisms we use to implement * ES6 Proxy objects are also useful wherever an object with weird behavior is * wanted. Proxies are used to implement: * * - the scope objects used by the Debugger's frame.eval() method * (see js::GetDebugScopeForFunction) * * - the khuey hack, whereby a whole compartment can be blown away * even if other compartments hold references to objects in it * (see js::NukeCrossCompartmentWrappers) * * - XPConnect security wrappers, which protect chrome from malicious content * (js/xpconnect/wrappers) * * - DOM objects with special property behavior, like named getters * (dom/bindings/Codegen.py generates these proxies from WebIDL) * * - semi-transparent use of objects that live in other processes * (CPOWs, implemented in js/ipc) * * ### Proxies and internal methods * * ES2016 specifies 13 internal methods. The runtime semantics of just * about everything a script can do to an object is specified in terms * of these internal methods. For example: * * JS code ES6 internal method that gets called * --------------------------- -------------------------------- * obj.prop obj.[[Get]](obj, "prop") * "prop" in obj obj.[[HasProperty]]("prop") * new obj() obj.[[Construct]]() * * With regard to the implementation of these internal methods, there are three * very different kinds of object in SpiderMonkey. * * 1. Native objects' internal methods are implemented in vm/NativeObject.cpp, * with duplicate (but functionally identical) implementations scattered * through the ICs and JITs. * * 2. Certain non-native objects have internal methods that are implemented as * magical js::ObjectOps hooks. We're trying to get rid of these. * * 3. All other objects are proxies. A proxy's internal methods are * implemented in C++, as the virtual methods of a C++ object stored on the * proxy, known as its handler. * * This means that just about anything you do to a proxy will end up going * through a C++ virtual method call. Possibly several. There's no reason the * JITs and ICs can't specialize for particular proxies, based on the handler; * but currently we don't do much of this, so the virtual method overhead * typically is actually incurred. * * ### The proxy handler hierarchy * * A major use case for proxies is to forward each internal method call to * another object, known as its target. The target can be an arbitrary JS * object. Not every proxy has the notion of a target, however. * * To minimize code duplication, a set of abstract proxy handler classes is * provided, from which other handlers may inherit. These abstract classes are * organized in the following hierarchy: * * BaseProxyHandler * | * Wrapper // has a target, can be unwrapped to reveal * | // target (see js::CheckedUnwrap) * | * CrossCompartmentWrapper // target is in another compartment; * // implements membrane between compartments * * Example: Some DOM objects (including all the arraylike DOM objects) are * implemented as proxies. Since these objects don't need to forward operations * to any underlying JS object, DOMJSProxyHandler directly subclasses * BaseProxyHandler. * * Gecko's security wrappers are examples of cross-compartment wrappers. * * ### Proxy prototype chains * * In addition to the normal methods, there are two models for proxy prototype * chains. * * 1. Proxies can use the standard prototype mechanism used throughout the * engine. To do so, simply pass a prototype to NewProxyObject() at * creation time. All prototype accesses will then "just work" to treat the * proxy as a "normal" object. * * 2. A proxy can implement more complicated prototype semantics (if, for * example, it wants to delegate the prototype lookup to a wrapped object) * by passing Proxy::LazyProto as the prototype at create time. This * guarantees that the getPrototype() handler method will be called every * time the object's prototype chain is accessed. * * This system is implemented with two methods: {get,set}Prototype. The * default implementation of setPrototype throws a TypeError. Since it is * not possible to create an object without a sense of prototype chain, * handlers must implement getPrototype if opting in to the dynamic * prototype system. */ /* * BaseProxyHandler is the most generic kind of proxy handler. It does not make * any assumptions about the target. Consequently, it does not provide any * default implementation for most methods. As a convenience, a few high-level * methods, like get() and set(), are given default implementations that work by * calling the low-level methods, like getOwnPropertyDescriptor(). * * Important: If you add a method here, you should probably also add a * Proxy::foo entry point with an AutoEnterPolicy. If you don't, you need an * explicit override for the method in SecurityWrapper. See bug 945826 comment 0. */ class JS_FRIEND_API(BaseProxyHandler) { /* * Sometimes it's desirable to designate groups of proxy handlers as "similar". * For this, we use the notion of a "family": A consumer-provided opaque pointer * that designates the larger group to which this proxy belongs. * * If it will never be important to differentiate this proxy from others as * part of a distinct group, nullptr may be used instead. */ const void* mFamily; /* * Proxy handlers can use mHasPrototype to request the following special * treatment from the JS engine: * * - When mHasPrototype is true, the engine never calls these methods: * getPropertyDescriptor, has, set, enumerate, iterate. Instead, for * these operations, it calls the "own" methods like * getOwnPropertyDescriptor, hasOwn, defineProperty, * getOwnEnumerablePropertyKeys, etc., and consults the prototype chain * if needed. * * - When mHasPrototype is true, the engine calls handler->get() only if * handler->hasOwn() says an own property exists on the proxy. If not, * it consults the prototype chain. * * This is useful because it frees the ProxyHandler from having to implement * any behavior having to do with the prototype chain. */ bool mHasPrototype; /* * All proxies indicate whether they have any sort of interesting security * policy that might prevent the caller from doing something it wants to * the object. In the case of wrappers, this distinction is used to * determine whether the caller may strip off the wrapper if it so desires. */ bool mHasSecurityPolicy; public: explicit constexpr BaseProxyHandler(const void* aFamily, bool aHasPrototype = false, bool aHasSecurityPolicy = false) : mFamily(aFamily), mHasPrototype(aHasPrototype), mHasSecurityPolicy(aHasSecurityPolicy) { } bool hasPrototype() const { return mHasPrototype; } bool hasSecurityPolicy() const { return mHasSecurityPolicy; } inline const void* family() const { return mFamily; } static size_t offsetOfFamily() { return offsetof(BaseProxyHandler, mFamily); } virtual bool finalizeInBackground(const Value& priv) const { /* * Called on creation of a proxy to determine whether its finalize * method can be finalized on the background thread. */ return true; } virtual bool canNurseryAllocate() const { /* * Nursery allocation is allowed if and only if it is safe to not * run |finalize| when the ProxyObject dies. */ return false; } /* Policy enforcement methods. * * enter() allows the policy to specify whether the caller may perform |act| * on the proxy's |id| property. In the case when |act| is CALL, |id| is * generally JSID_VOID. * * The |act| parameter to enter() specifies the action being performed. * If |bp| is false, the method suggests that the caller throw (though it * may still decide to squelch the error). * * We make these OR-able so that assertEnteredPolicy can pass a union of them. * For example, get{,Own}PropertyDescriptor is invoked by calls to ::get() * ::set(), in addition to being invoked on its own, so there are several * valid Actions that could have been entered. */ typedef uint32_t Action; enum { NONE = 0x00, GET = 0x01, SET = 0x02, CALL = 0x04, ENUMERATE = 0x08, GET_PROPERTY_DESCRIPTOR = 0x10 }; virtual bool enter(JSContext* cx, HandleObject wrapper, HandleId id, Action act, bool* bp) const; /* Standard internal methods. */ virtual bool getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id, MutableHandle desc) const = 0; virtual bool defineProperty(JSContext* cx, HandleObject proxy, HandleId id, Handle desc, ObjectOpResult& result) const = 0; virtual bool ownPropertyKeys(JSContext* cx, HandleObject proxy, AutoIdVector& props) const = 0; virtual bool delete_(JSContext* cx, HandleObject proxy, HandleId id, ObjectOpResult& result) const = 0; /* * These methods are standard, but the engine does not normally call them. * They're opt-in. See "Proxy prototype chains" above. * * getPrototype() crashes if called. setPrototype() throws a TypeError. */ virtual bool getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject protop) const; virtual bool setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto, ObjectOpResult& result) const; /* Non-standard but conceptual kin to {g,s}etPrototype, so these live here. */ virtual bool getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary, MutableHandleObject protop) const = 0; virtual bool setImmutablePrototype(JSContext* cx, HandleObject proxy, bool* succeeded) const; virtual bool preventExtensions(JSContext* cx, HandleObject proxy, ObjectOpResult& result) const = 0; virtual bool isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const = 0; /* * These standard internal methods are implemented, as a convenience, so * that ProxyHandler subclasses don't have to provide every single method. * * The base-class implementations work by calling getPropertyDescriptor(). * They do not follow any standard. When in doubt, override them. */ virtual bool has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const; virtual bool get(JSContext* cx, HandleObject proxy, HandleValue receiver, HandleId id, MutableHandleValue vp) const; virtual bool set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, HandleValue receiver, ObjectOpResult& result) const; /* * [[Call]] and [[Construct]] are standard internal methods but according * to the spec, they are not present on every object. * * SpiderMonkey never calls a proxy's call()/construct() internal method * unless isCallable()/isConstructor() returns true for that proxy. * * BaseProxyHandler::isCallable()/isConstructor() always return false, and * BaseProxyHandler::call()/construct() crash if called. So if you're * creating a kind of that is never callable, you don't have to override * anything, but otherwise you probably want to override all four. */ virtual bool call(JSContext* cx, HandleObject proxy, const CallArgs& args) const; virtual bool construct(JSContext* cx, HandleObject proxy, const CallArgs& args) const; /* SpiderMonkey extensions. */ virtual bool enumerate(JSContext* cx, HandleObject proxy, MutableHandleObject objp) const; virtual bool getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id, MutableHandle desc) const; virtual bool hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const; virtual bool getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy, AutoIdVector& props) const; virtual bool nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl, const CallArgs& args) const; virtual bool hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v, bool* bp) const; virtual bool getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const; virtual bool isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer) const; virtual const char* className(JSContext* cx, HandleObject proxy) const; virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const; virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const; virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const; virtual void trace(JSTracer* trc, JSObject* proxy) const; virtual void finalize(JSFreeOp* fop, JSObject* proxy) const; virtual void objectMoved(JSObject* proxy, const JSObject* old) const; // Allow proxies, wrappers in particular, to specify callability at runtime. // Note: These do not take const JSObject*, but they do in spirit. // We are not prepared to do this, as there's little const correctness // in the external APIs that handle proxies. virtual bool isCallable(JSObject* obj) const; virtual bool isConstructor(JSObject* obj) const; virtual bool getElements(JSContext* cx, HandleObject proxy, uint32_t begin, uint32_t end, ElementAdder* adder) const; /* See comment for weakmapKeyDelegateOp in js/Class.h. */ virtual JSObject* weakmapKeyDelegate(JSObject* proxy) const; virtual bool isScripted() const { return false; } }; extern JS_FRIEND_DATA(const js::Class* const) ProxyClassPtr; inline bool IsProxy(const JSObject* obj) { return GetObjectClass(obj)->isProxy(); } namespace detail { const uint32_t PROXY_EXTRA_SLOTS = 2; // Layout of the values stored by a proxy. Note that API clients require the // private slot to be the first slot in the proxy's values, so that the private // slot can be accessed in the same fashion as the first reserved slot, via // {Get,Set}ReservedOrProxyPrivateSlot. struct ProxyValueArray { Value privateSlot; Value extraSlots[PROXY_EXTRA_SLOTS]; ProxyValueArray() : privateSlot(JS::UndefinedValue()) { for (size_t i = 0; i < PROXY_EXTRA_SLOTS; i++) extraSlots[i] = JS::UndefinedValue(); } }; // All proxies share the same data layout. Following the object's shape and // type, the proxy has a ProxyDataLayout structure with a pointer to an array // of values and the proxy's handler. This is designed both so that proxies can // be easily swapped with other objects (via RemapWrapper) and to mimic the // layout of other objects (proxies and other objects have the same size) so // that common code can access either type of object. // // See GetReservedOrProxyPrivateSlot below. struct ProxyDataLayout { ProxyValueArray* values; const BaseProxyHandler* handler; }; const uint32_t ProxyDataOffset = 2 * sizeof(void*); inline ProxyDataLayout* GetProxyDataLayout(JSObject* obj) { MOZ_ASSERT(IsProxy(obj)); return reinterpret_cast(reinterpret_cast(obj) + ProxyDataOffset); } inline const ProxyDataLayout* GetProxyDataLayout(const JSObject* obj) { MOZ_ASSERT(IsProxy(obj)); return reinterpret_cast(reinterpret_cast(obj) + ProxyDataOffset); } } // namespace detail inline const BaseProxyHandler* GetProxyHandler(const JSObject* obj) { return detail::GetProxyDataLayout(obj)->handler; } inline const Value& GetProxyPrivate(const JSObject* obj) { return detail::GetProxyDataLayout(obj)->values->privateSlot; } inline JSObject* GetProxyTargetObject(JSObject* obj) { return GetProxyPrivate(obj).toObjectOrNull(); } inline const Value& GetProxyExtra(const JSObject* obj, size_t n) { MOZ_ASSERT(n < detail::PROXY_EXTRA_SLOTS); return detail::GetProxyDataLayout(obj)->values->extraSlots[n]; } inline void SetProxyHandler(JSObject* obj, const BaseProxyHandler* handler) { detail::GetProxyDataLayout(obj)->handler = handler; } JS_FRIEND_API(void) SetValueInProxy(Value* slot, const Value& value); inline void SetProxyExtra(JSObject* obj, size_t n, const Value& extra) { MOZ_ASSERT(n < detail::PROXY_EXTRA_SLOTS); Value* vp = &detail::GetProxyDataLayout(obj)->values->extraSlots[n]; // Trigger a barrier before writing the slot. if (vp->isGCThing() || extra.isGCThing()) SetValueInProxy(vp, extra); else *vp = extra; } inline bool IsScriptedProxy(const JSObject* obj) { return IsProxy(obj) && GetProxyHandler(obj)->isScripted(); } inline const Value& GetReservedOrProxyPrivateSlot(const JSObject* obj, size_t slot) { MOZ_ASSERT(slot == 0); MOZ_ASSERT(slot < JSCLASS_RESERVED_SLOTS(GetObjectClass(obj)) || IsProxy(obj)); return reinterpret_cast(obj)->slotRef(slot); } inline void SetReservedOrProxyPrivateSlot(JSObject* obj, size_t slot, const Value& value) { MOZ_ASSERT(slot == 0); MOZ_ASSERT(slot < JSCLASS_RESERVED_SLOTS(GetObjectClass(obj)) || IsProxy(obj)); shadow::Object* sobj = reinterpret_cast(obj); if (sobj->slotRef(slot).isGCThing() || value.isGCThing()) SetReservedOrProxyPrivateSlotWithBarrier(obj, slot, value); else sobj->slotRef(slot) = value; } class MOZ_STACK_CLASS ProxyOptions { protected: /* protected constructor for subclass */ explicit ProxyOptions(bool singletonArg, bool lazyProtoArg = false) : singleton_(singletonArg), lazyProto_(lazyProtoArg), clasp_(ProxyClassPtr) {} public: ProxyOptions() : singleton_(false), lazyProto_(false), clasp_(ProxyClassPtr) {} bool singleton() const { return singleton_; } ProxyOptions& setSingleton(bool flag) { singleton_ = flag; return *this; } bool lazyProto() const { return lazyProto_; } ProxyOptions& setLazyProto(bool flag) { lazyProto_ = flag; return *this; } const Class* clasp() const { return clasp_; } ProxyOptions& setClass(const Class* claspArg) { clasp_ = claspArg; return *this; } private: bool singleton_; bool lazyProto_; const Class* clasp_; }; JS_FRIEND_API(JSObject*) NewProxyObject(JSContext* cx, const BaseProxyHandler* handler, HandleValue priv, JSObject* proto, const ProxyOptions& options = ProxyOptions()); JSObject* RenewProxyObject(JSContext* cx, JSObject* obj, BaseProxyHandler* handler, const Value& priv); class JS_FRIEND_API(AutoEnterPolicy) { public: typedef BaseProxyHandler::Action Action; AutoEnterPolicy(JSContext* cx, const BaseProxyHandler* handler, HandleObject wrapper, HandleId id, Action act, bool mayThrow) #ifdef JS_DEBUG : context(nullptr) #endif { allow = handler->hasSecurityPolicy() ? handler->enter(cx, wrapper, id, act, &rv) : true; recordEnter(cx, wrapper, id, act); // We want to throw an exception if all of the following are true: // * The policy disallowed access. // * The policy set rv to false, indicating that we should throw. // * The caller did not instruct us to ignore exceptions. // * The policy did not throw itself. if (!allow && !rv && mayThrow) reportErrorIfExceptionIsNotPending(cx, id); } virtual ~AutoEnterPolicy() { recordLeave(); } inline bool allowed() { return allow; } inline bool returnValue() { MOZ_ASSERT(!allowed()); return rv; } protected: // no-op constructor for subclass AutoEnterPolicy() #ifdef JS_DEBUG : context(nullptr) , enteredAction(BaseProxyHandler::NONE) #endif {} void reportErrorIfExceptionIsNotPending(JSContext* cx, jsid id); bool allow; bool rv; #ifdef JS_DEBUG JSContext* context; mozilla::Maybe enteredProxy; mozilla::Maybe enteredId; Action enteredAction; // NB: We explicitly don't track the entered action here, because sometimes // set() methods do an implicit get() during their implementation, leading // to spurious assertions. AutoEnterPolicy* prev; void recordEnter(JSContext* cx, HandleObject proxy, HandleId id, Action act); void recordLeave(); friend JS_FRIEND_API(void) assertEnteredPolicy(JSContext* cx, JSObject* proxy, jsid id, Action act); #else inline void recordEnter(JSContext* cx, JSObject* proxy, jsid id, Action act) {} inline void recordLeave() {} #endif }; #ifdef JS_DEBUG class JS_FRIEND_API(AutoWaivePolicy) : public AutoEnterPolicy { public: AutoWaivePolicy(JSContext* cx, HandleObject proxy, HandleId id, BaseProxyHandler::Action act) { allow = true; recordEnter(cx, proxy, id, act); } }; #else class JS_FRIEND_API(AutoWaivePolicy) { public: AutoWaivePolicy(JSContext* cx, HandleObject proxy, HandleId id, BaseProxyHandler::Action act) {} }; #endif #ifdef JS_DEBUG extern JS_FRIEND_API(void) assertEnteredPolicy(JSContext* cx, JSObject* obj, jsid id, BaseProxyHandler::Action act); #else inline void assertEnteredPolicy(JSContext* cx, JSObject* obj, jsid id, BaseProxyHandler::Action act) {} #endif extern JS_FRIEND_API(JSObject*) InitProxyClass(JSContext* cx, JS::HandleObject obj); } /* namespace js */ #endif /* js_Proxy_h */