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