1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #ifndef js_Proxy_h
8 #define js_Proxy_h
9
10 #include "mozilla/Maybe.h"
11
12 #include "jstypes.h" // for JS_PUBLIC_API, JS_PUBLIC_DATA
13
14 #include "js/Array.h" // JS::IsArrayAnswer
15 #include "js/CallNonGenericMethod.h"
16 #include "js/Class.h"
17 #include "js/HeapAPI.h" // for ObjectIsMarkedBlack
18 #include "js/Id.h" // for jsid
19 #include "js/Object.h" // JS::GetClass
20 #include "js/RootingAPI.h" // for Handle, MutableHandle (ptr only)
21 #include "js/shadow/Object.h" // JS::shadow::Object
22 #include "js/TypeDecls.h" // for HandleObject, HandleId, HandleValue, MutableHandleIdVector, MutableHandleValue, MutableHand...
23 #include "js/Value.h" // for Value, AssertValueIsNotGray, UndefinedValue, ObjectOrNullValue
24
25 namespace js {
26
27 class RegExpShared;
28
29 class JS_PUBLIC_API Wrapper;
30
31 /*
32 * [SMDOC] Proxy Objects
33 *
34 * A proxy is a JSObject with highly customizable behavior. ES6 specifies a
35 * single kind of proxy, but the customization mechanisms we use to implement
36 * ES6 Proxy objects are also useful wherever an object with weird behavior is
37 * wanted. Proxies are used to implement:
38 *
39 * - the scope objects used by the Debugger's frame.eval() method
40 * (see js::GetDebugEnvironment)
41 *
42 * - the khuey hack, whereby a whole compartment can be blown away
43 * even if other compartments hold references to objects in it
44 * (see js::NukeCrossCompartmentWrappers)
45 *
46 * - XPConnect security wrappers, which protect chrome from malicious content
47 * (js/xpconnect/wrappers)
48 *
49 * - DOM objects with special property behavior, like named getters
50 * (dom/bindings/Codegen.py generates these proxies from WebIDL)
51 *
52 * ### Proxies and internal methods
53 *
54 * ES2019 specifies 13 internal methods. The runtime semantics of just about
55 * everything a script can do to an object is specified in terms of these
56 * internal methods. For example:
57 *
58 * JS code ES6 internal method that gets called
59 * --------------------------- --------------------------------
60 * obj.prop obj.[[Get]](obj, "prop")
61 * "prop" in obj obj.[[HasProperty]]("prop")
62 * new obj() obj.[[Construct]](<empty argument List>)
63 *
64 * With regard to the implementation of these internal methods, there are three
65 * very different kinds of object in SpiderMonkey.
66 *
67 * 1. Native objects cover most objects and contain both internal slots and
68 * properties. JSClassOps and ObjectOps may be used to override certain
69 * default behaviors.
70 *
71 * 2. Proxy objects are composed of internal slots and a ProxyHandler. The
72 * handler contains C++ methods that can implement these standard (and
73 * non-standard) internal methods. JSClassOps and ObjectOps for the base
74 * ProxyObject invoke the handler methods as appropriate.
75 *
76 * 3. Objects with custom layouts like TypedObjects. These rely on JSClassOps
77 * and ObjectOps to implement internal methods.
78 *
79 * Native objects with custom JSClassOps / ObjectOps are used when the object
80 * behaves very similar to a normal object such as the ArrayObject and it's
81 * length property. Most usages wrapping a C++ or other type should prefer
82 * using a Proxy. Using the proxy approach makes it much easier to create an
83 * ECMAScript and JIT compatible object, particularly if using an appropriate
84 * base class.
85 *
86 * Just about anything you do to a proxy will end up going through a C++
87 * virtual method call. Possibly several. There's no reason the JITs and ICs
88 * can't specialize for particular proxies, based on the handler; but currently
89 * we don't do much of this, so the virtual method overhead typically is
90 * actually incurred.
91 *
92 * ### The proxy handler hierarchy
93 *
94 * A major use case for proxies is to forward each internal method call to
95 * another object, known as its target. The target can be an arbitrary JS
96 * object. Not every proxy has the notion of a target, however.
97 *
98 * To minimize code duplication, a set of abstract proxy handler classes is
99 * provided, from which other handlers may inherit. These abstract classes are
100 * organized in the following hierarchy:
101 *
102 * BaseProxyHandler
103 * |
104 * ForwardingProxyHandler // has a target and forwards internal methods
105 * |
106 * Wrapper // can be unwrapped to reveal target
107 * | // (see js::CheckedUnwrap)
108 * |
109 * CrossCompartmentWrapper // target is in another compartment;
110 * // implements membrane between compartments
111 *
112 * Example: Some DOM objects (including all the arraylike DOM objects) are
113 * implemented as proxies. Since these objects don't need to forward operations
114 * to any underlying JS object, BaseDOMProxyHandler directly subclasses
115 * BaseProxyHandler.
116 *
117 * Gecko's security wrappers are examples of cross-compartment wrappers.
118 *
119 * ### Proxy prototype chains
120 *
121 * While most ECMAScript internal methods are handled by simply calling the
122 * handler method, the [[GetPrototypeOf]] / [[SetPrototypeOf]] behaviors may
123 * follow one of two models:
124 *
125 * 1. A concrete prototype object (or null) is passed to object construction
126 * and ordinary prototype read and write applies. The prototype-related
127 * handler hooks are never called in this case. The [[Prototype]] slot is
128 * used to store the current prototype value.
129 *
130 * 2. TaggedProto::LazyProto is passed to NewProxyObject (or the
131 * ProxyOptions::lazyProto flag is set). Each read or write of the
132 * prototype will invoke the handler. This dynamic prototype behavior may
133 * be useful for wrapper-like objects. If this mode is used the
134 * getPrototype handler at a minimum must be implemented.
135 *
136 * NOTE: In this mode the [[Prototype]] internal slot is unavailable and
137 * must be simulated if needed. This is non-standard, but an
138 * appropriate handler can hide this implementation detail.
139 *
140 * One subtlety here is that ECMAScript has a notion of "ordinary" prototypes.
141 * An object that doesn't override [[GetPrototypeOf]] is considered to have an
142 * ordinary prototype. The getPrototypeIfOrdinary handler must be implemented
143 * by you or your base class. Typically model 1 will be considered "ordinary"
144 * and model 2 will not.
145 */
146
147 /*
148 * BaseProxyHandler is the most generic kind of proxy handler. It does not make
149 * any assumptions about the target. Consequently, it does not provide any
150 * default implementation for most methods. As a convenience, a few high-level
151 * methods, like get() and set(), are given default implementations that work by
152 * calling the low-level methods, like getOwnPropertyDescriptor().
153 *
154 * Important: If you add a method here, you should probably also add a
155 * Proxy::foo entry point with an AutoEnterPolicy. If you don't, you need an
156 * explicit override for the method in SecurityWrapper. See bug 945826 comment
157 * 0.
158 */
159 class JS_PUBLIC_API BaseProxyHandler {
160 /*
161 * Sometimes it's desirable to designate groups of proxy handlers as
162 * "similar". For this, we use the notion of a "family": A consumer-provided
163 * opaque pointer that designates the larger group to which this proxy
164 * belongs.
165 *
166 * If it will never be important to differentiate this proxy from others as
167 * part of a distinct group, nullptr may be used instead.
168 */
169 const void* mFamily;
170
171 /*
172 * Proxy handlers can use mHasPrototype to request the following special
173 * treatment from the JS engine:
174 *
175 * - When mHasPrototype is true, the engine never calls these methods:
176 * has, set, enumerate, iterate. Instead, for these operations,
177 * it calls the "own" methods like getOwnPropertyDescriptor, hasOwn,
178 * defineProperty, getOwnEnumerablePropertyKeys, etc.,
179 * and consults the prototype chain if needed.
180 *
181 * - When mHasPrototype is true, the engine calls handler->get() only if
182 * handler->hasOwn() says an own property exists on the proxy. If not,
183 * it consults the prototype chain.
184 *
185 * This is useful because it frees the ProxyHandler from having to implement
186 * any behavior having to do with the prototype chain.
187 */
188 bool mHasPrototype;
189
190 /*
191 * All proxies indicate whether they have any sort of interesting security
192 * policy that might prevent the caller from doing something it wants to
193 * the object. In the case of wrappers, this distinction is used to
194 * determine whether the caller may strip off the wrapper if it so desires.
195 */
196 bool mHasSecurityPolicy;
197
198 public:
199 explicit constexpr BaseProxyHandler(const void* aFamily,
200 bool aHasPrototype = false,
201 bool aHasSecurityPolicy = false)
mFamily(aFamily)202 : mFamily(aFamily),
203 mHasPrototype(aHasPrototype),
204 mHasSecurityPolicy(aHasSecurityPolicy) {}
205
hasPrototype()206 bool hasPrototype() const { return mHasPrototype; }
207
hasSecurityPolicy()208 bool hasSecurityPolicy() const { return mHasSecurityPolicy; }
209
family()210 inline const void* family() const { return mFamily; }
offsetOfFamily()211 static size_t offsetOfFamily() { return offsetof(BaseProxyHandler, mFamily); }
212
finalizeInBackground(const JS::Value & priv)213 virtual bool finalizeInBackground(const JS::Value& priv) const {
214 /*
215 * Called on creation of a proxy to determine whether its finalize
216 * method can be finalized on the background thread.
217 */
218 return true;
219 }
220
canNurseryAllocate()221 virtual bool canNurseryAllocate() const {
222 /*
223 * Nursery allocation is allowed if and only if it is safe to not
224 * run |finalize| when the ProxyObject dies.
225 */
226 return false;
227 }
228
229 /* Policy enforcement methods.
230 *
231 * enter() allows the policy to specify whether the caller may perform |act|
232 * on the proxy's |id| property. In the case when |act| is CALL, |id| is
233 * generally JSID_VOID. The |mayThrow| parameter indicates whether a
234 * handler that wants to throw custom exceptions when denying should do so
235 * or not.
236 *
237 * The |act| parameter to enter() specifies the action being performed.
238 * If |bp| is false, the method suggests that the caller throw (though it
239 * may still decide to squelch the error).
240 *
241 * We make these OR-able so that assertEnteredPolicy can pass a union of them.
242 * For example, get{,Own}PropertyDescriptor is invoked by calls to ::get()
243 * ::set(), in addition to being invoked on its own, so there are several
244 * valid Actions that could have been entered.
245 */
246 typedef uint32_t Action;
247 enum {
248 NONE = 0x00,
249 GET = 0x01,
250 SET = 0x02,
251 CALL = 0x04,
252 ENUMERATE = 0x08,
253 GET_PROPERTY_DESCRIPTOR = 0x10
254 };
255
256 virtual bool enter(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id,
257 Action act, bool mayThrow, bool* bp) const;
258
259 /* Standard internal methods. */
260 virtual bool getOwnPropertyDescriptor(
261 JSContext* cx, JS::HandleObject proxy, JS::HandleId id,
262 JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc) const = 0;
263 virtual bool defineProperty(JSContext* cx, JS::HandleObject proxy,
264 JS::HandleId id,
265 JS::Handle<JS::PropertyDescriptor> desc,
266 JS::ObjectOpResult& result) const = 0;
267 virtual bool ownPropertyKeys(JSContext* cx, JS::HandleObject proxy,
268 JS::MutableHandleIdVector props) const = 0;
269 virtual bool delete_(JSContext* cx, JS::HandleObject proxy, JS::HandleId id,
270 JS::ObjectOpResult& result) const = 0;
271
272 /*
273 * These methods are standard, but the engine does not normally call them.
274 * They're opt-in. See "Proxy prototype chains" above.
275 *
276 * getPrototype() crashes if called. setPrototype() throws a TypeError.
277 */
278 virtual bool getPrototype(JSContext* cx, JS::HandleObject proxy,
279 JS::MutableHandleObject protop) const;
280 virtual bool setPrototype(JSContext* cx, JS::HandleObject proxy,
281 JS::HandleObject proto,
282 JS::ObjectOpResult& result) const;
283
284 /* Non-standard but conceptual kin to {g,s}etPrototype, so these live here. */
285 virtual bool getPrototypeIfOrdinary(JSContext* cx, JS::HandleObject proxy,
286 bool* isOrdinary,
287 JS::MutableHandleObject protop) const = 0;
288 virtual bool setImmutablePrototype(JSContext* cx, JS::HandleObject proxy,
289 bool* succeeded) const;
290
291 virtual bool preventExtensions(JSContext* cx, JS::HandleObject proxy,
292 JS::ObjectOpResult& result) const = 0;
293 virtual bool isExtensible(JSContext* cx, JS::HandleObject proxy,
294 bool* extensible) const = 0;
295
296 /*
297 * These standard internal methods are implemented, as a convenience, so
298 * that ProxyHandler subclasses don't have to provide every single method.
299 *
300 * The base-class implementations work by calling getOwnPropertyDescriptor()
301 * and going up the [[Prototype]] chain if necessary. The algorithm for this
302 * follows what is defined for Ordinary Objects in the ES spec.
303 * They do not follow any standard. When in doubt, override them.
304 */
305 virtual bool has(JSContext* cx, JS::HandleObject proxy, JS::HandleId id,
306 bool* bp) const;
307 virtual bool get(JSContext* cx, JS::HandleObject proxy,
308 JS::HandleValue receiver, JS::HandleId id,
309 JS::MutableHandleValue vp) const;
310 virtual bool set(JSContext* cx, JS::HandleObject proxy, JS::HandleId id,
311 JS::HandleValue v, JS::HandleValue receiver,
312 JS::ObjectOpResult& result) const;
313
314 // Use the ProxyExpando object for private fields, rather than taking the
315 // normal get/set/defineField paths.
useProxyExpandoObjectForPrivateFields()316 virtual bool useProxyExpandoObjectForPrivateFields() const { return true; }
317
318 /*
319 * [[Call]] and [[Construct]] are standard internal methods but according
320 * to the spec, they are not present on every object.
321 *
322 * SpiderMonkey never calls a proxy's call()/construct() internal method
323 * unless isCallable()/isConstructor() returns true for that proxy.
324 *
325 * BaseProxyHandler::isCallable()/isConstructor() always return false, and
326 * BaseProxyHandler::call()/construct() crash if called. So if you're
327 * creating a kind of that is never callable, you don't have to override
328 * anything, but otherwise you probably want to override all four.
329 */
330 virtual bool call(JSContext* cx, JS::HandleObject proxy,
331 const JS::CallArgs& args) const;
332 virtual bool construct(JSContext* cx, JS::HandleObject proxy,
333 const JS::CallArgs& args) const;
334
335 /* SpiderMonkey extensions. */
336 virtual bool enumerate(JSContext* cx, JS::HandleObject proxy,
337 JS::MutableHandleIdVector props) const;
338 virtual bool hasOwn(JSContext* cx, JS::HandleObject proxy, JS::HandleId id,
339 bool* bp) const;
340 virtual bool getOwnEnumerablePropertyKeys(
341 JSContext* cx, JS::HandleObject proxy,
342 JS::MutableHandleIdVector props) const;
343 virtual bool nativeCall(JSContext* cx, JS::IsAcceptableThis test,
344 JS::NativeImpl impl, const JS::CallArgs& args) const;
345 virtual bool hasInstance(JSContext* cx, JS::HandleObject proxy,
346 JS::MutableHandleValue v, bool* bp) const;
347 virtual bool getBuiltinClass(JSContext* cx, JS::HandleObject proxy,
348 ESClass* cls) const;
349 virtual bool isArray(JSContext* cx, JS::HandleObject proxy,
350 JS::IsArrayAnswer* answer) const;
351 virtual const char* className(JSContext* cx, JS::HandleObject proxy) const;
352 virtual JSString* fun_toString(JSContext* cx, JS::HandleObject proxy,
353 bool isToSource) const;
354 virtual RegExpShared* regexp_toShared(JSContext* cx,
355 JS::HandleObject proxy) const;
356 virtual bool boxedValue_unbox(JSContext* cx, JS::HandleObject proxy,
357 JS::MutableHandleValue vp) const;
358 virtual void trace(JSTracer* trc, JSObject* proxy) const;
359 virtual void finalize(JSFreeOp* fop, JSObject* proxy) const;
360 virtual size_t objectMoved(JSObject* proxy, JSObject* old) const;
361
362 // Allow proxies, wrappers in particular, to specify callability at runtime.
363 // Note: These do not take const JSObject*, but they do in spirit.
364 // We are not prepared to do this, as there's little const correctness
365 // in the external APIs that handle proxies.
366 virtual bool isCallable(JSObject* obj) const;
367 virtual bool isConstructor(JSObject* obj) const;
368
369 virtual bool getElements(JSContext* cx, JS::HandleObject proxy,
370 uint32_t begin, uint32_t end,
371 ElementAdder* adder) const;
372
isScripted()373 virtual bool isScripted() const { return false; }
374 };
375
376 extern JS_PUBLIC_DATA const JSClass ProxyClass;
377
IsProxy(const JSObject * obj)378 inline bool IsProxy(const JSObject* obj) {
379 return JS::GetClass(obj)->isProxyObject();
380 }
381
382 namespace detail {
383
384 // Proxy slot layout
385 // -----------------
386 //
387 // Every proxy has a ProxyValueArray that contains the following Values:
388 //
389 // - The expando slot. This is used to hold private fields should they be
390 // stamped into a non-forwarding proxy type.
391 // - The private slot.
392 // - The reserved slots. The number of slots is determined by the proxy's Class.
393 //
394 // Proxy objects store a pointer to the reserved slots (ProxyReservedSlots*).
395 // The ProxyValueArray and the private slot can be accessed using
396 // ProxyValueArray::fromReservedSlots or ProxyDataLayout::values.
397 //
398 // Storing a pointer to ProxyReservedSlots instead of ProxyValueArray has a
399 // number of advantages. In particular, it means JS::GetReservedSlot and
400 // JS::SetReservedSlot can be used with both proxies and native objects. This
401 // works because the ProxyReservedSlots* pointer is stored where native objects
402 // store their dynamic slots pointer.
403
404 struct ProxyReservedSlots {
405 JS::Value slots[1];
406
407 static inline int offsetOfPrivateSlot();
408
offsetOfSlotProxyReservedSlots409 static inline int offsetOfSlot(size_t slot) {
410 return offsetof(ProxyReservedSlots, slots[0]) + slot * sizeof(JS::Value);
411 }
412
initProxyReservedSlots413 void init(size_t nreserved) {
414 for (size_t i = 0; i < nreserved; i++) {
415 slots[i] = JS::UndefinedValue();
416 }
417 }
418
419 ProxyReservedSlots(const ProxyReservedSlots&) = delete;
420 void operator=(const ProxyReservedSlots&) = delete;
421 };
422
423 struct ProxyValueArray {
424 JS::Value expandoSlot;
425 JS::Value privateSlot;
426 ProxyReservedSlots reservedSlots;
427
initProxyValueArray428 void init(size_t nreserved) {
429 expandoSlot = JS::ObjectOrNullValue(nullptr);
430 privateSlot = JS::UndefinedValue();
431 reservedSlots.init(nreserved);
432 }
433
sizeOfProxyValueArray434 static size_t sizeOf(size_t nreserved) {
435 return offsetOfReservedSlots() + nreserved * sizeof(JS::Value);
436 }
fromReservedSlotsProxyValueArray437 static MOZ_ALWAYS_INLINE ProxyValueArray* fromReservedSlots(
438 ProxyReservedSlots* slots) {
439 uintptr_t p = reinterpret_cast<uintptr_t>(slots);
440 return reinterpret_cast<ProxyValueArray*>(p - offsetOfReservedSlots());
441 }
offsetOfReservedSlotsProxyValueArray442 static size_t offsetOfReservedSlots() {
443 return offsetof(ProxyValueArray, reservedSlots);
444 }
445
446 ProxyValueArray(const ProxyValueArray&) = delete;
447 void operator=(const ProxyValueArray&) = delete;
448 };
449
offsetOfPrivateSlot()450 /* static */ inline int ProxyReservedSlots::offsetOfPrivateSlot() {
451 return -int(ProxyValueArray::offsetOfReservedSlots()) +
452 offsetof(ProxyValueArray, privateSlot);
453 }
454
455 // All proxies share the same data layout. Following the object's shape and
456 // type, the proxy has a ProxyDataLayout structure with a pointer to an array
457 // of values and the proxy's handler. This is designed both so that proxies can
458 // be easily swapped with other objects (via RemapWrapper) and to mimic the
459 // layout of other objects (proxies and other objects have the same size) so
460 // that common code can access either type of object.
461 //
462 // See GetReservedOrProxyPrivateSlot below.
463 struct ProxyDataLayout {
464 ProxyReservedSlots* reservedSlots;
465 const BaseProxyHandler* handler;
466
valuesProxyDataLayout467 MOZ_ALWAYS_INLINE ProxyValueArray* values() const {
468 return ProxyValueArray::fromReservedSlots(reservedSlots);
469 }
470 };
471
472 #ifdef JS_64BIT
473 constexpr uint32_t ProxyDataOffset = 1 * sizeof(void*);
474 #else
475 constexpr uint32_t ProxyDataOffset = 2 * sizeof(void*);
476 #endif
477
GetProxyDataLayout(JSObject * obj)478 inline ProxyDataLayout* GetProxyDataLayout(JSObject* obj) {
479 MOZ_ASSERT(IsProxy(obj));
480 return reinterpret_cast<ProxyDataLayout*>(reinterpret_cast<uint8_t*>(obj) +
481 ProxyDataOffset);
482 }
483
GetProxyDataLayout(const JSObject * obj)484 inline const ProxyDataLayout* GetProxyDataLayout(const JSObject* obj) {
485 MOZ_ASSERT(IsProxy(obj));
486 return reinterpret_cast<const ProxyDataLayout*>(
487 reinterpret_cast<const uint8_t*>(obj) + ProxyDataOffset);
488 }
489
490 JS_PUBLIC_API void SetValueInProxy(JS::Value* slot, const JS::Value& value);
491
SetProxyReservedSlotUnchecked(JSObject * obj,size_t n,const JS::Value & extra)492 inline void SetProxyReservedSlotUnchecked(JSObject* obj, size_t n,
493 const JS::Value& extra) {
494 MOZ_ASSERT(n < JSCLASS_RESERVED_SLOTS(JS::GetClass(obj)));
495
496 JS::Value* vp = &GetProxyDataLayout(obj)->reservedSlots->slots[n];
497
498 // Trigger a barrier before writing the slot.
499 if (vp->isGCThing() || extra.isGCThing()) {
500 SetValueInProxy(vp, extra);
501 } else {
502 *vp = extra;
503 }
504 }
505
506 } // namespace detail
507
GetProxyHandler(const JSObject * obj)508 inline const BaseProxyHandler* GetProxyHandler(const JSObject* obj) {
509 return detail::GetProxyDataLayout(obj)->handler;
510 }
511
GetProxyPrivate(const JSObject * obj)512 inline const JS::Value& GetProxyPrivate(const JSObject* obj) {
513 return detail::GetProxyDataLayout(obj)->values()->privateSlot;
514 }
515
GetProxyExpando(const JSObject * obj)516 inline const JS::Value& GetProxyExpando(const JSObject* obj) {
517 return detail::GetProxyDataLayout(obj)->values()->expandoSlot;
518 }
519
GetProxyTargetObject(const JSObject * obj)520 inline JSObject* GetProxyTargetObject(const JSObject* obj) {
521 return GetProxyPrivate(obj).toObjectOrNull();
522 }
523
GetProxyReservedSlot(const JSObject * obj,size_t n)524 inline const JS::Value& GetProxyReservedSlot(const JSObject* obj, size_t n) {
525 MOZ_ASSERT(n < JSCLASS_RESERVED_SLOTS(JS::GetClass(obj)));
526 return detail::GetProxyDataLayout(obj)->reservedSlots->slots[n];
527 }
528
SetProxyHandler(JSObject * obj,const BaseProxyHandler * handler)529 inline void SetProxyHandler(JSObject* obj, const BaseProxyHandler* handler) {
530 detail::GetProxyDataLayout(obj)->handler = handler;
531 }
532
SetProxyReservedSlot(JSObject * obj,size_t n,const JS::Value & extra)533 inline void SetProxyReservedSlot(JSObject* obj, size_t n,
534 const JS::Value& extra) {
535 #ifdef DEBUG
536 if (gc::detail::ObjectIsMarkedBlack(obj)) {
537 JS::AssertValueIsNotGray(extra);
538 }
539 #endif
540
541 detail::SetProxyReservedSlotUnchecked(obj, n, extra);
542 }
543
SetProxyPrivate(JSObject * obj,const JS::Value & value)544 inline void SetProxyPrivate(JSObject* obj, const JS::Value& value) {
545 #ifdef DEBUG
546 if (gc::detail::ObjectIsMarkedBlack(obj)) {
547 JS::AssertValueIsNotGray(value);
548 }
549 #endif
550
551 JS::Value* vp = &detail::GetProxyDataLayout(obj)->values()->privateSlot;
552
553 // Trigger a barrier before writing the slot.
554 if (vp->isGCThing() || value.isGCThing()) {
555 detail::SetValueInProxy(vp, value);
556 } else {
557 *vp = value;
558 }
559 }
560
IsScriptedProxy(const JSObject * obj)561 inline bool IsScriptedProxy(const JSObject* obj) {
562 return IsProxy(obj) && GetProxyHandler(obj)->isScripted();
563 }
564
565 class MOZ_STACK_CLASS ProxyOptions {
566 protected:
567 /* protected constructor for subclass */
ProxyOptions(bool lazyProtoArg)568 explicit ProxyOptions(bool lazyProtoArg)
569 : lazyProto_(lazyProtoArg), clasp_(&ProxyClass) {}
570
571 public:
ProxyOptions()572 ProxyOptions() : ProxyOptions(false) {}
573
lazyProto()574 bool lazyProto() const { return lazyProto_; }
setLazyProto(bool flag)575 ProxyOptions& setLazyProto(bool flag) {
576 lazyProto_ = flag;
577 return *this;
578 }
579
clasp()580 const JSClass* clasp() const { return clasp_; }
setClass(const JSClass * claspArg)581 ProxyOptions& setClass(const JSClass* claspArg) {
582 clasp_ = claspArg;
583 return *this;
584 }
585
586 private:
587 bool lazyProto_;
588 const JSClass* clasp_;
589 };
590
591 JS_PUBLIC_API JSObject* NewProxyObject(
592 JSContext* cx, const BaseProxyHandler* handler, JS::HandleValue priv,
593 JSObject* proto, const ProxyOptions& options = ProxyOptions());
594
595 JSObject* RenewProxyObject(JSContext* cx, JSObject* obj,
596 BaseProxyHandler* handler, const JS::Value& priv);
597
598 class JS_PUBLIC_API AutoEnterPolicy {
599 public:
600 typedef BaseProxyHandler::Action Action;
AutoEnterPolicy(JSContext * cx,const BaseProxyHandler * handler,JS::HandleObject wrapper,JS::HandleId id,Action act,bool mayThrow)601 AutoEnterPolicy(JSContext* cx, const BaseProxyHandler* handler,
602 JS::HandleObject wrapper, JS::HandleId id, Action act,
603 bool mayThrow)
604 #ifdef JS_DEBUG
605 : context(nullptr)
606 #endif
607 {
608 allow = handler->hasSecurityPolicy()
609 ? handler->enter(cx, wrapper, id, act, mayThrow, &rv)
610 : true;
611 recordEnter(cx, wrapper, id, act);
612 // We want to throw an exception if all of the following are true:
613 // * The policy disallowed access.
614 // * The policy set rv to false, indicating that we should throw.
615 // * The caller did not instruct us to ignore exceptions.
616 // * The policy did not throw itself.
617 if (!allow && !rv && mayThrow) {
618 reportErrorIfExceptionIsNotPending(cx, id);
619 }
620 }
621
~AutoEnterPolicy()622 virtual ~AutoEnterPolicy() { recordLeave(); }
allowed()623 inline bool allowed() { return allow; }
returnValue()624 inline bool returnValue() {
625 MOZ_ASSERT(!allowed());
626 return rv;
627 }
628
629 protected:
630 // no-op constructor for subclass
AutoEnterPolicy()631 AutoEnterPolicy()
632 #ifdef JS_DEBUG
633 : context(nullptr),
634 enteredAction(BaseProxyHandler::NONE)
635 #endif
636 {
637 }
638 void reportErrorIfExceptionIsNotPending(JSContext* cx, JS::HandleId id);
639 bool allow;
640 bool rv;
641
642 #ifdef JS_DEBUG
643 JSContext* context;
644 mozilla::Maybe<JS::HandleObject> enteredProxy;
645 mozilla::Maybe<JS::HandleId> enteredId;
646 Action enteredAction;
647
648 // NB: We explicitly don't track the entered action here, because sometimes
649 // set() methods do an implicit get() during their implementation, leading
650 // to spurious assertions.
651 AutoEnterPolicy* prev;
652 void recordEnter(JSContext* cx, JS::HandleObject proxy, JS::HandleId id,
653 Action act);
654 void recordLeave();
655
656 friend JS_PUBLIC_API void assertEnteredPolicy(JSContext* cx, JSObject* proxy,
657 jsid id, Action act);
658 #else
recordEnter(JSContext * cx,JSObject * proxy,jsid id,Action act)659 inline void recordEnter(JSContext* cx, JSObject* proxy, jsid id, Action act) {
660 }
recordLeave()661 inline void recordLeave() {}
662 #endif
663
664 private:
665 // This operator needs to be deleted explicitly, otherwise Visual C++ will
666 // create it automatically when it is part of the export JS API. In that
667 // case, compile would fail because HandleId is not allowed to be assigned
668 // and consequently instantiation of assign operator of mozilla::Maybe
669 // would fail. See bug 1325351 comment 16. Copy constructor is removed at
670 // the same time for consistency.
671 AutoEnterPolicy(const AutoEnterPolicy&) = delete;
672 AutoEnterPolicy& operator=(const AutoEnterPolicy&) = delete;
673 };
674
675 #ifdef JS_DEBUG
676 class JS_PUBLIC_API AutoWaivePolicy : public AutoEnterPolicy {
677 public:
AutoWaivePolicy(JSContext * cx,JS::HandleObject proxy,JS::HandleId id,BaseProxyHandler::Action act)678 AutoWaivePolicy(JSContext* cx, JS::HandleObject proxy, JS::HandleId id,
679 BaseProxyHandler::Action act) {
680 allow = true;
681 recordEnter(cx, proxy, id, act);
682 }
683 };
684 #else
685 class JS_PUBLIC_API AutoWaivePolicy {
686 public:
AutoWaivePolicy(JSContext * cx,JS::HandleObject proxy,JS::HandleId id,BaseProxyHandler::Action act)687 AutoWaivePolicy(JSContext* cx, JS::HandleObject proxy, JS::HandleId id,
688 BaseProxyHandler::Action act) {}
689 };
690 #endif
691
692 #ifdef JS_DEBUG
693 extern JS_PUBLIC_API void assertEnteredPolicy(JSContext* cx, JSObject* obj,
694 jsid id,
695 BaseProxyHandler::Action act);
696 #else
assertEnteredPolicy(JSContext * cx,JSObject * obj,jsid id,BaseProxyHandler::Action act)697 inline void assertEnteredPolicy(JSContext* cx, JSObject* obj, jsid id,
698 BaseProxyHandler::Action act) {}
699 #endif
700
701 extern JS_PUBLIC_DATA const JSClassOps ProxyClassOps;
702 extern JS_PUBLIC_DATA const js::ClassExtension ProxyClassExtension;
703 extern JS_PUBLIC_DATA const js::ObjectOps ProxyObjectOps;
704
705 template <unsigned Flags>
CheckProxyFlags()706 constexpr unsigned CheckProxyFlags() {
707 constexpr size_t reservedSlots =
708 (Flags >> JSCLASS_RESERVED_SLOTS_SHIFT) & JSCLASS_RESERVED_SLOTS_MASK;
709
710 // For now assert each Proxy Class has at least 1 reserved slot. This is
711 // not a hard requirement, but helps catch Classes that need an explicit
712 // JSCLASS_HAS_RESERVED_SLOTS since bug 1360523.
713 static_assert(reservedSlots > 0,
714 "Proxy Classes must have at least 1 reserved slot");
715
716 constexpr size_t numSlots =
717 offsetof(js::detail::ProxyValueArray, reservedSlots) / sizeof(JS::Value);
718
719 // ProxyValueArray must fit inline in the object, so assert the number of
720 // slots does not exceed MAX_FIXED_SLOTS.
721 static_assert(numSlots + reservedSlots <= JS::shadow::Object::MAX_FIXED_SLOTS,
722 "ProxyValueArray size must not exceed max JSObject size");
723
724 // Proxies must not have the JSCLASS_SKIP_NURSERY_FINALIZE flag set: they
725 // always have finalizers, and whether they can be nursery allocated is
726 // controlled by the canNurseryAllocate() method on the proxy handler.
727 static_assert(!(Flags & JSCLASS_SKIP_NURSERY_FINALIZE),
728 "Proxies must not use JSCLASS_SKIP_NURSERY_FINALIZE; use "
729 "the canNurseryAllocate() proxy handler method instead.");
730 return Flags;
731 }
732
733 #define PROXY_CLASS_DEF_WITH_CLASS_SPEC(name, flags, classSpec) \
734 { \
735 name, \
736 JSClass::NON_NATIVE | JSCLASS_IS_PROXY | \
737 JSCLASS_DELAY_METADATA_BUILDER | js::CheckProxyFlags<flags>(), \
738 &js::ProxyClassOps, classSpec, &js::ProxyClassExtension, \
739 &js::ProxyObjectOps \
740 }
741
742 #define PROXY_CLASS_DEF(name, flags) \
743 PROXY_CLASS_DEF_WITH_CLASS_SPEC(name, flags, JS_NULL_CLASS_SPEC)
744
745 // Converts a proxy into a DeadObjectProxy that will throw exceptions on all
746 // access. This will run the proxy's finalizer to perform clean-up before the
747 // conversion happens.
748 JS_PUBLIC_API void NukeNonCCWProxy(JSContext* cx, JS::HandleObject proxy);
749
750 // This is a variant of js::NukeNonCCWProxy() for CCWs. It should only be called
751 // on CCWs that have been removed from CCW tables.
752 JS_PUBLIC_API void NukeRemovedCrossCompartmentWrapper(JSContext* cx,
753 JSObject* wrapper);
754
755 } /* namespace js */
756
757 #endif /* js_Proxy_h */
758