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/. */
7 #ifndef vm_Compartment_inl_h
8 #define vm_Compartment_inl_h
10 #include "vm/Compartment.h"
12 #include <type_traits>
15 #include "jsfriendapi.h"
17 #include "js/CallArgs.h"
18 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
19 #include "js/Wrapper.h"
20 #include "vm/Iteration.h"
21 #include "vm/JSObject.h"
23 #include "vm/JSContext-inl.h"
27 inline js::StringWrapperMap::Ptr
JS::Compartment::lookupWrapper(
28 JSString
* str
) const {
29 return zone()->crossZoneStringWrappers().lookup(str
);
32 inline bool JS::Compartment::wrap(JSContext
* cx
, JS::MutableHandleValue vp
) {
33 /* Only GC things have to be wrapped or copied. */
34 if (!vp
.isGCThing()) {
39 * Symbols are GC things, but never need to be wrapped or copied because
40 * they are always allocated in the atoms zone. They still need to be
41 * marked in the new compartment's zone, however.
44 cx
->markAtomValue(vp
);
50 JS::RootedString
str(cx
, vp
.toString());
51 if (!wrap(cx
, &str
)) {
59 JS::RootedBigInt
bi(cx
, vp
.toBigInt());
67 #ifdef ENABLE_RECORD_TUPLE
68 if (vp
.isExtendedPrimitive()) {
69 JS::RootedObject
extPrim(cx
, &vp
.toExtendedPrimitive());
70 if (!wrapExtendedPrimitive(cx
, &extPrim
)) {
73 vp
.setExtendedPrimitive(*extPrim
);
78 MOZ_ASSERT(vp
.isObject());
81 * All that's left are objects.
83 * Object wrapping isn't the fastest thing in the world, in part because
84 * we have to unwrap and invoke the prewrap hook to find the identity
85 * object before we even start checking the cache. Neither of these
86 * operations are needed in the common case, where we're just wrapping
87 * a plain JS object from the wrappee's side of the membrane to the
90 * To optimize this, we note that the cache should only ever contain
91 * identity objects - that is to say, objects that serve as the
92 * canonical representation for a unique object identity observable by
93 * script. Unwrap and prewrap are both steps that we take to get to the
94 * identity of an incoming objects, and as such, they shuld never map
95 * one identity object to another object. This means that we can safely
96 * check the cache immediately, and only risk false negatives. Do this
97 * in opt builds, and do both in debug builds so that we can assert
98 * that we get the same answer.
101 JS::AssertValueIsNotGray(vp
);
102 JS::RootedObject
cacheResult(cx
);
104 if (js::ObjectWrapperMap::Ptr p
= lookupWrapper(&vp
.toObject())) {
106 cacheResult
= p
->value().get();
108 vp
.setObject(*p
->value().get());
113 JS::RootedObject
obj(cx
, &vp
.toObject());
114 if (!wrap(cx
, &obj
)) {
118 MOZ_ASSERT_IF(cacheResult
, obj
== cacheResult
);
122 inline bool JS::Compartment::wrap(JSContext
* cx
,
123 MutableHandle
<mozilla::Maybe
<Value
>> vp
) {
124 if (vp
.get().isNothing()) {
128 return wrap(cx
, MutableHandle
<Value
>::fromMarkedLocation(vp
.get().ptr()));
135 * Return the name of class T as a static null-terminated ASCII string constant
136 * (for error messages).
139 const char* ClassName() {
140 return T::class_
.name
;
143 template <class T
, class ErrorCallback
>
144 [[nodiscard
]] T
* UnwrapAndTypeCheckValueSlowPath(JSContext
* cx
,
146 ErrorCallback throwTypeError
) {
147 JSObject
* obj
= nullptr;
148 if (value
.isObject()) {
149 obj
= &value
.toObject();
150 if (IsWrapper(obj
)) {
151 obj
= CheckedUnwrapStatic(obj
);
153 ReportAccessDenied(cx
);
159 if (!obj
|| !obj
->is
<T
>()) {
164 return &obj
->as
<T
>();
167 template <class ErrorCallback
>
168 [[nodiscard
]] JSObject
* UnwrapAndTypeCheckValueSlowPath(
169 JSContext
* cx
, HandleValue value
, const JSClass
* clasp
,
170 ErrorCallback throwTypeError
) {
171 JSObject
* obj
= nullptr;
172 if (value
.isObject()) {
173 obj
= &value
.toObject();
174 if (IsWrapper(obj
)) {
175 obj
= CheckedUnwrapStatic(obj
);
177 ReportAccessDenied(cx
);
183 if (!obj
|| !obj
->hasClass(clasp
)) {
191 } // namespace detail
194 * Remove all wrappers from `val` and try to downcast the result to class `T`.
196 * DANGER: The result may not be same-compartment with `cx`.
198 * This calls `throwTypeError` if the value isn't an object, cannot be
199 * unwrapped, or isn't an instance of the expected type. `throwTypeError` must
200 * in fact throw a TypeError (or OOM trying).
202 template <class T
, class ErrorCallback
>
203 [[nodiscard
]] inline T
* UnwrapAndTypeCheckValue(JSContext
* cx
,
205 ErrorCallback throwTypeError
) {
208 static_assert(!std::is_convertible_v
<T
*, Wrapper
*>,
209 "T can't be a Wrapper type; this function discards wrappers");
211 if (value
.isObject() && value
.toObject().is
<T
>()) {
212 return &value
.toObject().as
<T
>();
215 return detail::UnwrapAndTypeCheckValueSlowPath
<T
>(cx
, value
, throwTypeError
);
219 * Remove all wrappers from |val| and try to downcast the result to an object of
222 * DANGER: The result may not be same-compartment with |cx|.
224 * This calls |throwTypeError| if the value isn't an object, cannot be
225 * unwrapped, or isn't an instance of the expected type. |throwTypeError| must
226 * in fact throw a TypeError (or OOM trying).
228 template <class ErrorCallback
>
229 [[nodiscard
]] inline JSObject
* UnwrapAndTypeCheckValue(
230 JSContext
* cx
, HandleValue value
, const JSClass
* clasp
,
231 ErrorCallback throwTypeError
) {
234 if (value
.isObject() && value
.toObject().hasClass(clasp
)) {
235 return &value
.toObject();
238 return detail::UnwrapAndTypeCheckValueSlowPath(cx
, value
, clasp
,
243 * Remove all wrappers from `args.thisv()` and try to downcast the result to
246 * DANGER: The result may not be same-compartment with `cx`.
248 * This throws a TypeError if the value isn't an object, cannot be unwrapped,
249 * or isn't an instance of the expected type.
252 [[nodiscard
]] inline T
* UnwrapAndTypeCheckThis(JSContext
* cx
,
253 const CallArgs
& args
,
254 const char* methodName
) {
255 HandleValue thisv
= args
.thisv();
256 return UnwrapAndTypeCheckValue
<T
>(cx
, thisv
, [cx
, methodName
, thisv
] {
257 JS_ReportErrorNumberLatin1(cx
, GetErrorMessage
, nullptr,
258 JSMSG_INCOMPATIBLE_PROTO
, detail::ClassName
<T
>(),
259 methodName
, InformalValueTypeName(thisv
));
264 * Remove all wrappers from `args[argIndex]` and try to downcast the result to
267 * DANGER: The result may not be same-compartment with `cx`.
269 * This throws a TypeError if the specified argument is missing, isn't an
270 * object, cannot be unwrapped, or isn't an instance of the expected type.
273 [[nodiscard
]] inline T
* UnwrapAndTypeCheckArgument(JSContext
* cx
,
275 const char* methodName
,
277 HandleValue val
= args
.get(argIndex
);
278 return UnwrapAndTypeCheckValue
<T
>(cx
, val
, [cx
, val
, methodName
, argIndex
] {
279 Int32ToCStringBuf cbuf
;
280 char* numStr
= Int32ToCString(&cbuf
, argIndex
+ 1);
282 JS_ReportErrorNumberLatin1(
283 cx
, GetErrorMessage
, nullptr, JSMSG_WRONG_TYPE_ARG
, numStr
, methodName
,
284 detail::ClassName
<T
>(), InformalValueTypeName(val
));
289 * Unwrap an object of a known type.
291 * If `obj` is an object of class T, this returns a pointer to that object. If
292 * `obj` is a wrapper for such an object, this tries to unwrap the object and
293 * return a pointer to it. If access is denied, or `obj` was a wrapper but has
294 * been nuked, this reports an error and returns null.
296 * In all other cases, the behavior is undefined, so call this only if `obj` is
297 * known to have been an object of class T, or a wrapper to a T, at some point.
300 [[nodiscard
]] inline T
* UnwrapAndDowncastObject(JSContext
* cx
, JSObject
* obj
) {
301 static_assert(!std::is_convertible_v
<T
*, Wrapper
*>,
302 "T can't be a Wrapper type; this function discards wrappers");
305 if (JS_IsDeadWrapper(obj
)) {
306 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
311 // It would probably be OK to do an unchecked unwrap here, but we allow
312 // arbitrary security policies, so check anyway.
313 obj
= obj
->maybeUnwrapAs
<T
>();
315 ReportAccessDenied(cx
);
320 return &obj
->as
<T
>();
324 * Unwrap an object of a known (but not compile-time-known) class.
326 * If |obj| is an object with class |clasp|, this returns |obj|. If |obj| is a
327 * wrapper for such an object, this tries to unwrap the object and return a
328 * pointer to it. If access is denied, or |obj| was a wrapper but has been
329 * nuked, this reports an error and returns null.
331 * In all other cases, the behavior is undefined, so call this only if |obj| is
332 * known to have had class |clasp|, or been a wrapper to such an object, at some
335 [[nodiscard
]] inline JSObject
* UnwrapAndDowncastObject(JSContext
* cx
,
337 const JSClass
* clasp
) {
339 if (JS_IsDeadWrapper(obj
)) {
340 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
345 // It would probably be OK to do an unchecked unwrap here, but we allow
346 // arbitrary security policies, so check anyway.
347 obj
= obj
->maybeUnwrapAs(clasp
);
349 ReportAccessDenied(cx
);
354 MOZ_ASSERT(obj
->hasClass(clasp
));
359 * Unwrap a value of a known type. See UnwrapAndDowncastObject.
362 [[nodiscard
]] inline T
* UnwrapAndDowncastValue(JSContext
* cx
,
363 const Value
& value
) {
364 return UnwrapAndDowncastObject
<T
>(cx
, &value
.toObject());
368 * Unwrap an object of a known (but not compile-time-known) class. See
369 * UnwrapAndDowncastObject.
371 [[nodiscard
]] inline JSObject
* UnwrapAndDowncastValue(JSContext
* cx
,
373 const JSClass
* clasp
) {
374 return UnwrapAndDowncastObject(cx
, &value
.toObject(), clasp
);
378 * Read a private slot that is known to point to a particular type of object.
380 * Some internal slots specified in various standards effectively have static
381 * types. For example, the [[ownerReadableStream]] slot of a stream reader is
382 * guaranteed to be a ReadableStream. However, because of compartments, we
383 * sometimes store a cross-compartment wrapper in that slot. And since wrappers
384 * can be nuked, that wrapper may become a dead object proxy.
386 * UnwrapInternalSlot() copes with the cross-compartment and dead object cases,
387 * but not plain bugs where the slot hasn't been initialized or doesn't contain
388 * the expected type of object. Call this only if the slot is certain to
389 * contain either an instance of T, a wrapper for a T, or a dead object.
391 * `cx` and `unwrappedObj` are not required to be same-compartment.
393 * DANGER: The result may not be same-compartment with either `cx` or `obj`.
396 [[nodiscard
]] inline T
* UnwrapInternalSlot(JSContext
* cx
,
397 Handle
<NativeObject
*> unwrappedObj
,
399 static_assert(!std::is_convertible_v
<T
*, Wrapper
*>,
400 "T can't be a Wrapper type; this function discards wrappers");
402 return UnwrapAndDowncastValue
<T
>(cx
, unwrappedObj
->getFixedSlot(slot
));
406 * Read a function slot that is known to point to a particular type of object.
408 * This is like UnwrapInternalSlot, but for extended function slots. Call this
409 * only if the specified slot is known to have been initialized with an object
410 * of class T or a wrapper for such an object.
412 * DANGER: The result may not be same-compartment with `cx`.
415 [[nodiscard
]] T
* UnwrapCalleeSlot(JSContext
* cx
, CallArgs
& args
,
416 size_t extendedSlot
) {
417 JSFunction
& func
= args
.callee().as
<JSFunction
>();
418 return UnwrapAndDowncastValue
<T
>(cx
, func
.getExtendedSlot(extendedSlot
));
423 MOZ_ALWAYS_INLINE
bool JS::Compartment::objectMaybeInIteration(JSObject
* obj
) {
424 MOZ_ASSERT(obj
->compartment() == this);
426 js::NativeIteratorListIter
iter(&enumerators_
);
428 // If the list is empty, we're not iterating any objects.
433 // If the list contains a single object, check if it's |obj|.
434 js::NativeIterator
* next
= iter
.next();
436 return next
->objectBeingIterated() == obj
;
442 #endif /* vm_Compartment_inl_h */