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