Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / js / src / vm / Compartment-inl.h
blob43aff5275060fb55c75edfa503ce4ebeed9045a3
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>
14 #include "jsapi.h"
15 #include "jsfriendapi.h"
16 #include "jsnum.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"
25 struct JSClass;
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()) {
35 return true;
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.
43 if (vp.isSymbol()) {
44 cx->markAtomValue(vp);
45 return true;
48 /* Handle strings. */
49 if (vp.isString()) {
50 JS::RootedString str(cx, vp.toString());
51 if (!wrap(cx, &str)) {
52 return false;
54 vp.setString(str);
55 return true;
58 if (vp.isBigInt()) {
59 JS::RootedBigInt bi(cx, vp.toBigInt());
60 if (!wrap(cx, &bi)) {
61 return false;
63 vp.setBigInt(bi);
64 return true;
67 #ifdef ENABLE_RECORD_TUPLE
68 if (vp.isExtendedPrimitive()) {
69 JS::RootedObject extPrim(cx, &vp.toExtendedPrimitive());
70 if (!wrapExtendedPrimitive(cx, &extPrim)) {
71 return false;
73 vp.setExtendedPrimitive(*extPrim);
74 return true;
76 #endif
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
88 * wrapper's side.
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.
100 #ifdef DEBUG
101 JS::AssertValueIsNotGray(vp);
102 JS::RootedObject cacheResult(cx);
103 #endif
104 if (js::ObjectWrapperMap::Ptr p = lookupWrapper(&vp.toObject())) {
105 #ifdef DEBUG
106 cacheResult = p->value().get();
107 #else
108 vp.setObject(*p->value().get());
109 return true;
110 #endif
113 JS::RootedObject obj(cx, &vp.toObject());
114 if (!wrap(cx, &obj)) {
115 return false;
117 vp.setObject(*obj);
118 MOZ_ASSERT_IF(cacheResult, obj == cacheResult);
119 return true;
122 inline bool JS::Compartment::wrap(JSContext* cx,
123 MutableHandle<mozilla::Maybe<Value>> vp) {
124 if (vp.get().isNothing()) {
125 return true;
128 return wrap(cx, MutableHandle<Value>::fromMarkedLocation(vp.get().ptr()));
131 namespace js {
132 namespace detail {
135 * Return the name of class T as a static null-terminated ASCII string constant
136 * (for error messages).
138 template <class T>
139 const char* ClassName() {
140 return T::class_.name;
143 template <class T, class ErrorCallback>
144 [[nodiscard]] T* UnwrapAndTypeCheckValueSlowPath(JSContext* cx,
145 HandleValue value,
146 ErrorCallback throwTypeError) {
147 JSObject* obj = nullptr;
148 if (value.isObject()) {
149 obj = &value.toObject();
150 if (IsWrapper(obj)) {
151 obj = CheckedUnwrapStatic(obj);
152 if (!obj) {
153 ReportAccessDenied(cx);
154 return nullptr;
159 if (!obj || !obj->is<T>()) {
160 throwTypeError();
161 return nullptr;
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);
176 if (!obj) {
177 ReportAccessDenied(cx);
178 return nullptr;
183 if (!obj || !obj->hasClass(clasp)) {
184 throwTypeError();
185 return nullptr;
188 return obj;
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,
204 HandleValue value,
205 ErrorCallback throwTypeError) {
206 cx->check(value);
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
220 * the class |clasp|.
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) {
232 cx->check(value);
234 if (value.isObject() && value.toObject().hasClass(clasp)) {
235 return &value.toObject();
238 return detail::UnwrapAndTypeCheckValueSlowPath(cx, value, clasp,
239 throwTypeError);
243 * Remove all wrappers from `args.thisv()` and try to downcast the result to
244 * class `T`.
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.
251 template <class T>
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
265 * class `T`.
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.
272 template <class T>
273 [[nodiscard]] inline T* UnwrapAndTypeCheckArgument(JSContext* cx,
274 CallArgs& args,
275 const char* methodName,
276 int argIndex) {
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);
281 MOZ_ASSERT(numStr);
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.
299 template <class T>
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");
304 if (IsProxy(obj)) {
305 if (JS_IsDeadWrapper(obj)) {
306 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
307 JSMSG_DEAD_OBJECT);
308 return 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>();
314 if (!obj) {
315 ReportAccessDenied(cx);
316 return nullptr;
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
333 * point.
335 [[nodiscard]] inline JSObject* UnwrapAndDowncastObject(JSContext* cx,
336 JSObject* obj,
337 const JSClass* clasp) {
338 if (IsProxy(obj)) {
339 if (JS_IsDeadWrapper(obj)) {
340 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
341 JSMSG_DEAD_OBJECT);
342 return 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);
348 if (!obj) {
349 ReportAccessDenied(cx);
350 return nullptr;
354 MOZ_ASSERT(obj->hasClass(clasp));
355 return obj;
359 * Unwrap a value of a known type. See UnwrapAndDowncastObject.
361 template <class T>
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,
372 const Value& value,
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`.
395 template <class T>
396 [[nodiscard]] inline T* UnwrapInternalSlot(JSContext* cx,
397 Handle<NativeObject*> unwrappedObj,
398 uint32_t slot) {
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`.
414 template <class T>
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));
421 } // namespace js
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.
429 if (iter.done()) {
430 return false;
433 // If the list contains a single object, check if it's |obj|.
434 js::NativeIterator* next = iter.next();
435 if (iter.done()) {
436 return next->objectBeingIterated() == obj;
439 return true;
442 #endif /* vm_Compartment_inl_h */