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 vm_Compartment_inl_h
8 #define vm_Compartment_inl_h
9 
10 #include "vm/Compartment.h"
11 
12 #include <type_traits>
13 
14 #include "jsapi.h"
15 #include "jsfriendapi.h"
16 #include "jsnum.h"
17 #include "gc/Barrier.h"
18 #include "gc/Marking.h"
19 #include "js/CallArgs.h"
20 #include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
21 #include "js/Wrapper.h"
22 #include "vm/Iteration.h"
23 #include "vm/JSObject.h"
24 
25 #include "vm/JSContext-inl.h"
26 
27 struct JSClass;
28 
lookupWrapper(JSString * str)29 inline js::StringWrapperMap::Ptr JS::Compartment::lookupWrapper(
30     JSString* str) const {
31   return zone()->crossZoneStringWrappers().lookup(str);
32 }
33 
wrap(JSContext * cx,JS::MutableHandleValue vp)34 inline bool JS::Compartment::wrap(JSContext* cx, JS::MutableHandleValue vp) {
35   /* Only GC things have to be wrapped or copied. */
36   if (!vp.isGCThing()) {
37     return true;
38   }
39 
40   /*
41    * Symbols are GC things, but never need to be wrapped or copied because
42    * they are always allocated in the atoms zone. They still need to be
43    * marked in the new compartment's zone, however.
44    */
45   if (vp.isSymbol()) {
46     cx->markAtomValue(vp);
47     return true;
48   }
49 
50   /* Handle strings. */
51   if (vp.isString()) {
52     JS::RootedString str(cx, vp.toString());
53     if (!wrap(cx, &str)) {
54       return false;
55     }
56     vp.setString(str);
57     return true;
58   }
59 
60   if (vp.isBigInt()) {
61     JS::RootedBigInt bi(cx, vp.toBigInt());
62     if (!wrap(cx, &bi)) {
63       return false;
64     }
65     vp.setBigInt(bi);
66     return true;
67   }
68 
69   MOZ_ASSERT(vp.isObject());
70 
71   /*
72    * All that's left are objects.
73    *
74    * Object wrapping isn't the fastest thing in the world, in part because
75    * we have to unwrap and invoke the prewrap hook to find the identity
76    * object before we even start checking the cache. Neither of these
77    * operations are needed in the common case, where we're just wrapping
78    * a plain JS object from the wrappee's side of the membrane to the
79    * wrapper's side.
80    *
81    * To optimize this, we note that the cache should only ever contain
82    * identity objects - that is to say, objects that serve as the
83    * canonical representation for a unique object identity observable by
84    * script. Unwrap and prewrap are both steps that we take to get to the
85    * identity of an incoming objects, and as such, they shuld never map
86    * one identity object to another object. This means that we can safely
87    * check the cache immediately, and only risk false negatives. Do this
88    * in opt builds, and do both in debug builds so that we can assert
89    * that we get the same answer.
90    */
91 #ifdef DEBUG
92   JS::AssertValueIsNotGray(vp);
93   JS::RootedObject cacheResult(cx);
94 #endif
95   if (js::ObjectWrapperMap::Ptr p = lookupWrapper(&vp.toObject())) {
96 #ifdef DEBUG
97     cacheResult = p->value().get();
98 #else
99     vp.setObject(*p->value().get());
100     return true;
101 #endif
102   }
103 
104   JS::RootedObject obj(cx, &vp.toObject());
105   if (!wrap(cx, &obj)) {
106     return false;
107   }
108   vp.setObject(*obj);
109   MOZ_ASSERT_IF(cacheResult, obj == cacheResult);
110   return true;
111 }
112 
wrap(JSContext * cx,MutableHandle<mozilla::Maybe<Value>> vp)113 inline bool JS::Compartment::wrap(JSContext* cx,
114                                   MutableHandle<mozilla::Maybe<Value>> vp) {
115   if (vp.get().isNothing()) {
116     return true;
117   }
118 
119   return wrap(cx, MutableHandle<Value>::fromMarkedLocation(vp.get().ptr()));
120 }
121 
122 namespace js {
123 namespace detail {
124 
125 /**
126  * Return the name of class T as a static null-terminated ASCII string constant
127  * (for error messages).
128  */
129 template <class T>
ClassName()130 const char* ClassName() {
131   return T::class_.name;
132 }
133 
134 template <class T, class ErrorCallback>
UnwrapAndTypeCheckValueSlowPath(JSContext * cx,HandleValue value,ErrorCallback throwTypeError)135 [[nodiscard]] T* UnwrapAndTypeCheckValueSlowPath(JSContext* cx,
136                                                  HandleValue value,
137                                                  ErrorCallback throwTypeError) {
138   JSObject* obj = nullptr;
139   if (value.isObject()) {
140     obj = &value.toObject();
141     if (IsWrapper(obj)) {
142       obj = CheckedUnwrapStatic(obj);
143       if (!obj) {
144         ReportAccessDenied(cx);
145         return nullptr;
146       }
147     }
148   }
149 
150   if (!obj || !obj->is<T>()) {
151     throwTypeError();
152     return nullptr;
153   }
154 
155   return &obj->as<T>();
156 }
157 
158 template <class ErrorCallback>
UnwrapAndTypeCheckValueSlowPath(JSContext * cx,HandleValue value,const JSClass * clasp,ErrorCallback throwTypeError)159 [[nodiscard]] JSObject* UnwrapAndTypeCheckValueSlowPath(
160     JSContext* cx, HandleValue value, const JSClass* clasp,
161     ErrorCallback throwTypeError) {
162   JSObject* obj = nullptr;
163   if (value.isObject()) {
164     obj = &value.toObject();
165     if (IsWrapper(obj)) {
166       obj = CheckedUnwrapStatic(obj);
167       if (!obj) {
168         ReportAccessDenied(cx);
169         return nullptr;
170       }
171     }
172   }
173 
174   if (!obj || !obj->hasClass(clasp)) {
175     throwTypeError();
176     return nullptr;
177   }
178 
179   return obj;
180 }
181 
182 }  // namespace detail
183 
184 /**
185  * Remove all wrappers from `val` and try to downcast the result to class `T`.
186  *
187  * DANGER: The result may not be same-compartment with `cx`.
188  *
189  * This calls `throwTypeError` if the value isn't an object, cannot be
190  * unwrapped, or isn't an instance of the expected type. `throwTypeError` must
191  * in fact throw a TypeError (or OOM trying).
192  */
193 template <class T, class ErrorCallback>
UnwrapAndTypeCheckValue(JSContext * cx,HandleValue value,ErrorCallback throwTypeError)194 [[nodiscard]] inline T* UnwrapAndTypeCheckValue(JSContext* cx,
195                                                 HandleValue value,
196                                                 ErrorCallback throwTypeError) {
197   cx->check(value);
198 
199   static_assert(!std::is_convertible_v<T*, Wrapper*>,
200                 "T can't be a Wrapper type; this function discards wrappers");
201 
202   if (value.isObject() && value.toObject().is<T>()) {
203     return &value.toObject().as<T>();
204   }
205 
206   return detail::UnwrapAndTypeCheckValueSlowPath<T>(cx, value, throwTypeError);
207 }
208 
209 /**
210  * Remove all wrappers from |val| and try to downcast the result to an object of
211  * the class |clasp|.
212  *
213  * DANGER: The result may not be same-compartment with |cx|.
214  *
215  * This calls |throwTypeError| if the value isn't an object, cannot be
216  * unwrapped, or isn't an instance of the expected type.  |throwTypeError| must
217  * in fact throw a TypeError (or OOM trying).
218  */
219 template <class ErrorCallback>
UnwrapAndTypeCheckValue(JSContext * cx,HandleValue value,const JSClass * clasp,ErrorCallback throwTypeError)220 [[nodiscard]] inline JSObject* UnwrapAndTypeCheckValue(
221     JSContext* cx, HandleValue value, const JSClass* clasp,
222     ErrorCallback throwTypeError) {
223   cx->check(value);
224 
225   if (value.isObject() && value.toObject().hasClass(clasp)) {
226     return &value.toObject();
227   }
228 
229   return detail::UnwrapAndTypeCheckValueSlowPath(cx, value, clasp,
230                                                  throwTypeError);
231 }
232 
233 /**
234  * Remove all wrappers from `args.thisv()` and try to downcast the result to
235  * class `T`.
236  *
237  * DANGER: The result may not be same-compartment with `cx`.
238  *
239  * This throws a TypeError if the value isn't an object, cannot be unwrapped,
240  * or isn't an instance of the expected type.
241  */
242 template <class T>
UnwrapAndTypeCheckThis(JSContext * cx,const CallArgs & args,const char * methodName)243 [[nodiscard]] inline T* UnwrapAndTypeCheckThis(JSContext* cx,
244                                                const CallArgs& args,
245                                                const char* methodName) {
246   HandleValue thisv = args.thisv();
247   return UnwrapAndTypeCheckValue<T>(cx, thisv, [cx, methodName, thisv] {
248     JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr,
249                                JSMSG_INCOMPATIBLE_PROTO, detail::ClassName<T>(),
250                                methodName, InformalValueTypeName(thisv));
251   });
252 }
253 
254 /**
255  * Remove all wrappers from `args[argIndex]` and try to downcast the result to
256  * class `T`.
257  *
258  * DANGER: The result may not be same-compartment with `cx`.
259  *
260  * This throws a TypeError if the specified argument is missing, isn't an
261  * object, cannot be unwrapped, or isn't an instance of the expected type.
262  */
263 template <class T>
UnwrapAndTypeCheckArgument(JSContext * cx,CallArgs & args,const char * methodName,int argIndex)264 [[nodiscard]] inline T* UnwrapAndTypeCheckArgument(JSContext* cx,
265                                                    CallArgs& args,
266                                                    const char* methodName,
267                                                    int argIndex) {
268   HandleValue val = args.get(argIndex);
269   return UnwrapAndTypeCheckValue<T>(cx, val, [cx, val, methodName, argIndex] {
270     ToCStringBuf cbuf;
271     if (char* numStr = NumberToCString(cx, &cbuf, argIndex + 1, 10)) {
272       JS_ReportErrorNumberLatin1(
273           cx, GetErrorMessage, nullptr, JSMSG_WRONG_TYPE_ARG, numStr,
274           methodName, detail::ClassName<T>(), InformalValueTypeName(val));
275     } else {
276       ReportOutOfMemory(cx);
277     }
278   });
279 }
280 
281 /**
282  * Unwrap an object of a known type.
283  *
284  * If `obj` is an object of class T, this returns a pointer to that object. If
285  * `obj` is a wrapper for such an object, this tries to unwrap the object and
286  * return a pointer to it. If access is denied, or `obj` was a wrapper but has
287  * been nuked, this reports an error and returns null.
288  *
289  * In all other cases, the behavior is undefined, so call this only if `obj` is
290  * known to have been an object of class T, or a wrapper to a T, at some point.
291  */
292 template <class T>
UnwrapAndDowncastObject(JSContext * cx,JSObject * obj)293 [[nodiscard]] inline T* UnwrapAndDowncastObject(JSContext* cx, JSObject* obj) {
294   static_assert(!std::is_convertible_v<T*, Wrapper*>,
295                 "T can't be a Wrapper type; this function discards wrappers");
296 
297   if (IsProxy(obj)) {
298     if (JS_IsDeadWrapper(obj)) {
299       JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
300                                 JSMSG_DEAD_OBJECT);
301       return nullptr;
302     }
303 
304     // It would probably be OK to do an unchecked unwrap here, but we allow
305     // arbitrary security policies, so check anyway.
306     obj = obj->maybeUnwrapAs<T>();
307     if (!obj) {
308       ReportAccessDenied(cx);
309       return nullptr;
310     }
311   }
312 
313   return &obj->as<T>();
314 }
315 
316 /**
317  * Unwrap an object of a known (but not compile-time-known) class.
318  *
319  * If |obj| is an object with class |clasp|, this returns |obj|.  If |obj| is a
320  * wrapper for such an object, this tries to unwrap the object and return a
321  * pointer to it.  If access is denied, or |obj| was a wrapper but has been
322  * nuked, this reports an error and returns null.
323  *
324  * In all other cases, the behavior is undefined, so call this only if |obj| is
325  * known to have had class |clasp|, or been a wrapper to such an object, at some
326  * point.
327  */
UnwrapAndDowncastObject(JSContext * cx,JSObject * obj,const JSClass * clasp)328 [[nodiscard]] inline JSObject* UnwrapAndDowncastObject(JSContext* cx,
329                                                        JSObject* obj,
330                                                        const JSClass* clasp) {
331   if (IsProxy(obj)) {
332     if (JS_IsDeadWrapper(obj)) {
333       JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
334                                 JSMSG_DEAD_OBJECT);
335       return nullptr;
336     }
337 
338     // It would probably be OK to do an unchecked unwrap here, but we allow
339     // arbitrary security policies, so check anyway.
340     obj = obj->maybeUnwrapAs(clasp);
341     if (!obj) {
342       ReportAccessDenied(cx);
343       return nullptr;
344     }
345   }
346 
347   MOZ_ASSERT(obj->hasClass(clasp));
348   return obj;
349 }
350 
351 /**
352  * Unwrap a value of a known type. See UnwrapAndDowncastObject.
353  */
354 template <class T>
UnwrapAndDowncastValue(JSContext * cx,const Value & value)355 [[nodiscard]] inline T* UnwrapAndDowncastValue(JSContext* cx,
356                                                const Value& value) {
357   return UnwrapAndDowncastObject<T>(cx, &value.toObject());
358 }
359 
360 /**
361  * Unwrap an object of a known (but not compile-time-known) class.  See
362  * UnwrapAndDowncastObject.
363  */
UnwrapAndDowncastValue(JSContext * cx,const Value & value,const JSClass * clasp)364 [[nodiscard]] inline JSObject* UnwrapAndDowncastValue(JSContext* cx,
365                                                       const Value& value,
366                                                       const JSClass* clasp) {
367   return UnwrapAndDowncastObject(cx, &value.toObject(), clasp);
368 }
369 
370 /**
371  * Read a private slot that is known to point to a particular type of object.
372  *
373  * Some internal slots specified in various standards effectively have static
374  * types. For example, the [[ownerReadableStream]] slot of a stream reader is
375  * guaranteed to be a ReadableStream. However, because of compartments, we
376  * sometimes store a cross-compartment wrapper in that slot. And since wrappers
377  * can be nuked, that wrapper may become a dead object proxy.
378  *
379  * UnwrapInternalSlot() copes with the cross-compartment and dead object cases,
380  * but not plain bugs where the slot hasn't been initialized or doesn't contain
381  * the expected type of object. Call this only if the slot is certain to
382  * contain either an instance of T, a wrapper for a T, or a dead object.
383  *
384  * `cx` and `unwrappedObj` are not required to be same-compartment.
385  *
386  * DANGER: The result may not be same-compartment with either `cx` or `obj`.
387  */
388 template <class T>
UnwrapInternalSlot(JSContext * cx,Handle<NativeObject * > unwrappedObj,uint32_t slot)389 [[nodiscard]] inline T* UnwrapInternalSlot(JSContext* cx,
390                                            Handle<NativeObject*> unwrappedObj,
391                                            uint32_t slot) {
392   static_assert(!std::is_convertible_v<T*, Wrapper*>,
393                 "T can't be a Wrapper type; this function discards wrappers");
394 
395   return UnwrapAndDowncastValue<T>(cx, unwrappedObj->getFixedSlot(slot));
396 }
397 
398 /**
399  * Read a function slot that is known to point to a particular type of object.
400  *
401  * This is like UnwrapInternalSlot, but for extended function slots. Call this
402  * only if the specified slot is known to have been initialized with an object
403  * of class T or a wrapper for such an object.
404  *
405  * DANGER: The result may not be same-compartment with `cx`.
406  */
407 template <class T>
UnwrapCalleeSlot(JSContext * cx,CallArgs & args,size_t extendedSlot)408 [[nodiscard]] T* UnwrapCalleeSlot(JSContext* cx, CallArgs& args,
409                                   size_t extendedSlot) {
410   JSFunction& func = args.callee().as<JSFunction>();
411   return UnwrapAndDowncastValue<T>(cx, func.getExtendedSlot(extendedSlot));
412 }
413 
414 }  // namespace js
415 
416 #endif /* vm_Compartment_inl_h */
417