diff options
Diffstat (limited to 'js/src/vm/Compartment-inl.h')
-rw-r--r-- | js/src/vm/Compartment-inl.h | 442 |
1 files changed, 442 insertions, 0 deletions
diff --git a/js/src/vm/Compartment-inl.h b/js/src/vm/Compartment-inl.h new file mode 100644 index 0000000000..43aff52750 --- /dev/null +++ b/js/src/vm/Compartment-inl.h @@ -0,0 +1,442 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef vm_Compartment_inl_h +#define vm_Compartment_inl_h + +#include "vm/Compartment.h" + +#include <type_traits> + +#include "jsapi.h" +#include "jsfriendapi.h" +#include "jsnum.h" +#include "js/CallArgs.h" +#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* +#include "js/Wrapper.h" +#include "vm/Iteration.h" +#include "vm/JSObject.h" + +#include "vm/JSContext-inl.h" + +struct JSClass; + +inline js::StringWrapperMap::Ptr JS::Compartment::lookupWrapper( + JSString* str) const { + return zone()->crossZoneStringWrappers().lookup(str); +} + +inline bool JS::Compartment::wrap(JSContext* cx, JS::MutableHandleValue vp) { + /* Only GC things have to be wrapped or copied. */ + if (!vp.isGCThing()) { + return true; + } + + /* + * Symbols are GC things, but never need to be wrapped or copied because + * they are always allocated in the atoms zone. They still need to be + * marked in the new compartment's zone, however. + */ + if (vp.isSymbol()) { + cx->markAtomValue(vp); + return true; + } + + /* Handle strings. */ + if (vp.isString()) { + JS::RootedString str(cx, vp.toString()); + if (!wrap(cx, &str)) { + return false; + } + vp.setString(str); + return true; + } + + if (vp.isBigInt()) { + JS::RootedBigInt bi(cx, vp.toBigInt()); + if (!wrap(cx, &bi)) { + return false; + } + vp.setBigInt(bi); + return true; + } + +#ifdef ENABLE_RECORD_TUPLE + if (vp.isExtendedPrimitive()) { + JS::RootedObject extPrim(cx, &vp.toExtendedPrimitive()); + if (!wrapExtendedPrimitive(cx, &extPrim)) { + return false; + } + vp.setExtendedPrimitive(*extPrim); + return true; + } +#endif + + MOZ_ASSERT(vp.isObject()); + + /* + * All that's left are objects. + * + * Object wrapping isn't the fastest thing in the world, in part because + * we have to unwrap and invoke the prewrap hook to find the identity + * object before we even start checking the cache. Neither of these + * operations are needed in the common case, where we're just wrapping + * a plain JS object from the wrappee's side of the membrane to the + * wrapper's side. + * + * To optimize this, we note that the cache should only ever contain + * identity objects - that is to say, objects that serve as the + * canonical representation for a unique object identity observable by + * script. Unwrap and prewrap are both steps that we take to get to the + * identity of an incoming objects, and as such, they shuld never map + * one identity object to another object. This means that we can safely + * check the cache immediately, and only risk false negatives. Do this + * in opt builds, and do both in debug builds so that we can assert + * that we get the same answer. + */ +#ifdef DEBUG + JS::AssertValueIsNotGray(vp); + JS::RootedObject cacheResult(cx); +#endif + if (js::ObjectWrapperMap::Ptr p = lookupWrapper(&vp.toObject())) { +#ifdef DEBUG + cacheResult = p->value().get(); +#else + vp.setObject(*p->value().get()); + return true; +#endif + } + + JS::RootedObject obj(cx, &vp.toObject()); + if (!wrap(cx, &obj)) { + return false; + } + vp.setObject(*obj); + MOZ_ASSERT_IF(cacheResult, obj == cacheResult); + return true; +} + +inline bool JS::Compartment::wrap(JSContext* cx, + MutableHandle<mozilla::Maybe<Value>> vp) { + if (vp.get().isNothing()) { + return true; + } + + return wrap(cx, MutableHandle<Value>::fromMarkedLocation(vp.get().ptr())); +} + +namespace js { +namespace detail { + +/** + * Return the name of class T as a static null-terminated ASCII string constant + * (for error messages). + */ +template <class T> +const char* ClassName() { + return T::class_.name; +} + +template <class T, class ErrorCallback> +[[nodiscard]] T* UnwrapAndTypeCheckValueSlowPath(JSContext* cx, + HandleValue value, + ErrorCallback throwTypeError) { + JSObject* obj = nullptr; + if (value.isObject()) { + obj = &value.toObject(); + if (IsWrapper(obj)) { + obj = CheckedUnwrapStatic(obj); + if (!obj) { + ReportAccessDenied(cx); + return nullptr; + } + } + } + + if (!obj || !obj->is<T>()) { + throwTypeError(); + return nullptr; + } + + return &obj->as<T>(); +} + +template <class ErrorCallback> +[[nodiscard]] JSObject* UnwrapAndTypeCheckValueSlowPath( + JSContext* cx, HandleValue value, const JSClass* clasp, + ErrorCallback throwTypeError) { + JSObject* obj = nullptr; + if (value.isObject()) { + obj = &value.toObject(); + if (IsWrapper(obj)) { + obj = CheckedUnwrapStatic(obj); + if (!obj) { + ReportAccessDenied(cx); + return nullptr; + } + } + } + + if (!obj || !obj->hasClass(clasp)) { + throwTypeError(); + return nullptr; + } + + return obj; +} + +} // namespace detail + +/** + * Remove all wrappers from `val` and try to downcast the result to class `T`. + * + * DANGER: The result may not be same-compartment with `cx`. + * + * This calls `throwTypeError` if the value isn't an object, cannot be + * unwrapped, or isn't an instance of the expected type. `throwTypeError` must + * in fact throw a TypeError (or OOM trying). + */ +template <class T, class ErrorCallback> +[[nodiscard]] inline T* UnwrapAndTypeCheckValue(JSContext* cx, + HandleValue value, + ErrorCallback throwTypeError) { + cx->check(value); + + static_assert(!std::is_convertible_v<T*, Wrapper*>, + "T can't be a Wrapper type; this function discards wrappers"); + + if (value.isObject() && value.toObject().is<T>()) { + return &value.toObject().as<T>(); + } + + return detail::UnwrapAndTypeCheckValueSlowPath<T>(cx, value, throwTypeError); +} + +/** + * Remove all wrappers from |val| and try to downcast the result to an object of + * the class |clasp|. + * + * DANGER: The result may not be same-compartment with |cx|. + * + * This calls |throwTypeError| if the value isn't an object, cannot be + * unwrapped, or isn't an instance of the expected type. |throwTypeError| must + * in fact throw a TypeError (or OOM trying). + */ +template <class ErrorCallback> +[[nodiscard]] inline JSObject* UnwrapAndTypeCheckValue( + JSContext* cx, HandleValue value, const JSClass* clasp, + ErrorCallback throwTypeError) { + cx->check(value); + + if (value.isObject() && value.toObject().hasClass(clasp)) { + return &value.toObject(); + } + + return detail::UnwrapAndTypeCheckValueSlowPath(cx, value, clasp, + throwTypeError); +} + +/** + * Remove all wrappers from `args.thisv()` and try to downcast the result to + * class `T`. + * + * DANGER: The result may not be same-compartment with `cx`. + * + * This throws a TypeError if the value isn't an object, cannot be unwrapped, + * or isn't an instance of the expected type. + */ +template <class T> +[[nodiscard]] inline T* UnwrapAndTypeCheckThis(JSContext* cx, + const CallArgs& args, + const char* methodName) { + HandleValue thisv = args.thisv(); + return UnwrapAndTypeCheckValue<T>(cx, thisv, [cx, methodName, thisv] { + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + JSMSG_INCOMPATIBLE_PROTO, detail::ClassName<T>(), + methodName, InformalValueTypeName(thisv)); + }); +} + +/** + * Remove all wrappers from `args[argIndex]` and try to downcast the result to + * class `T`. + * + * DANGER: The result may not be same-compartment with `cx`. + * + * This throws a TypeError if the specified argument is missing, isn't an + * object, cannot be unwrapped, or isn't an instance of the expected type. + */ +template <class T> +[[nodiscard]] inline T* UnwrapAndTypeCheckArgument(JSContext* cx, + CallArgs& args, + const char* methodName, + int argIndex) { + HandleValue val = args.get(argIndex); + return UnwrapAndTypeCheckValue<T>(cx, val, [cx, val, methodName, argIndex] { + Int32ToCStringBuf cbuf; + char* numStr = Int32ToCString(&cbuf, argIndex + 1); + MOZ_ASSERT(numStr); + JS_ReportErrorNumberLatin1( + cx, GetErrorMessage, nullptr, JSMSG_WRONG_TYPE_ARG, numStr, methodName, + detail::ClassName<T>(), InformalValueTypeName(val)); + }); +} + +/** + * Unwrap an object of a known type. + * + * If `obj` is an object of class T, this returns a pointer to that object. If + * `obj` is a wrapper for such an object, this tries to unwrap the object and + * return a pointer to it. If access is denied, or `obj` was a wrapper but has + * been nuked, this reports an error and returns null. + * + * In all other cases, the behavior is undefined, so call this only if `obj` is + * known to have been an object of class T, or a wrapper to a T, at some point. + */ +template <class T> +[[nodiscard]] inline T* UnwrapAndDowncastObject(JSContext* cx, JSObject* obj) { + static_assert(!std::is_convertible_v<T*, Wrapper*>, + "T can't be a Wrapper type; this function discards wrappers"); + + if (IsProxy(obj)) { + if (JS_IsDeadWrapper(obj)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_DEAD_OBJECT); + return nullptr; + } + + // It would probably be OK to do an unchecked unwrap here, but we allow + // arbitrary security policies, so check anyway. + obj = obj->maybeUnwrapAs<T>(); + if (!obj) { + ReportAccessDenied(cx); + return nullptr; + } + } + + return &obj->as<T>(); +} + +/** + * Unwrap an object of a known (but not compile-time-known) class. + * + * If |obj| is an object with class |clasp|, this returns |obj|. If |obj| is a + * wrapper for such an object, this tries to unwrap the object and return a + * pointer to it. If access is denied, or |obj| was a wrapper but has been + * nuked, this reports an error and returns null. + * + * In all other cases, the behavior is undefined, so call this only if |obj| is + * known to have had class |clasp|, or been a wrapper to such an object, at some + * point. + */ +[[nodiscard]] inline JSObject* UnwrapAndDowncastObject(JSContext* cx, + JSObject* obj, + const JSClass* clasp) { + if (IsProxy(obj)) { + if (JS_IsDeadWrapper(obj)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_DEAD_OBJECT); + return nullptr; + } + + // It would probably be OK to do an unchecked unwrap here, but we allow + // arbitrary security policies, so check anyway. + obj = obj->maybeUnwrapAs(clasp); + if (!obj) { + ReportAccessDenied(cx); + return nullptr; + } + } + + MOZ_ASSERT(obj->hasClass(clasp)); + return obj; +} + +/** + * Unwrap a value of a known type. See UnwrapAndDowncastObject. + */ +template <class T> +[[nodiscard]] inline T* UnwrapAndDowncastValue(JSContext* cx, + const Value& value) { + return UnwrapAndDowncastObject<T>(cx, &value.toObject()); +} + +/** + * Unwrap an object of a known (but not compile-time-known) class. See + * UnwrapAndDowncastObject. + */ +[[nodiscard]] inline JSObject* UnwrapAndDowncastValue(JSContext* cx, + const Value& value, + const JSClass* clasp) { + return UnwrapAndDowncastObject(cx, &value.toObject(), clasp); +} + +/** + * Read a private slot that is known to point to a particular type of object. + * + * Some internal slots specified in various standards effectively have static + * types. For example, the [[ownerReadableStream]] slot of a stream reader is + * guaranteed to be a ReadableStream. However, because of compartments, we + * sometimes store a cross-compartment wrapper in that slot. And since wrappers + * can be nuked, that wrapper may become a dead object proxy. + * + * UnwrapInternalSlot() copes with the cross-compartment and dead object cases, + * but not plain bugs where the slot hasn't been initialized or doesn't contain + * the expected type of object. Call this only if the slot is certain to + * contain either an instance of T, a wrapper for a T, or a dead object. + * + * `cx` and `unwrappedObj` are not required to be same-compartment. + * + * DANGER: The result may not be same-compartment with either `cx` or `obj`. + */ +template <class T> +[[nodiscard]] inline T* UnwrapInternalSlot(JSContext* cx, + Handle<NativeObject*> unwrappedObj, + uint32_t slot) { + static_assert(!std::is_convertible_v<T*, Wrapper*>, + "T can't be a Wrapper type; this function discards wrappers"); + + return UnwrapAndDowncastValue<T>(cx, unwrappedObj->getFixedSlot(slot)); +} + +/** + * Read a function slot that is known to point to a particular type of object. + * + * This is like UnwrapInternalSlot, but for extended function slots. Call this + * only if the specified slot is known to have been initialized with an object + * of class T or a wrapper for such an object. + * + * DANGER: The result may not be same-compartment with `cx`. + */ +template <class T> +[[nodiscard]] T* UnwrapCalleeSlot(JSContext* cx, CallArgs& args, + size_t extendedSlot) { + JSFunction& func = args.callee().as<JSFunction>(); + return UnwrapAndDowncastValue<T>(cx, func.getExtendedSlot(extendedSlot)); +} + +} // namespace js + +MOZ_ALWAYS_INLINE bool JS::Compartment::objectMaybeInIteration(JSObject* obj) { + MOZ_ASSERT(obj->compartment() == this); + + js::NativeIteratorListIter iter(&enumerators_); + + // If the list is empty, we're not iterating any objects. + if (iter.done()) { + return false; + } + + // If the list contains a single object, check if it's |obj|. + js::NativeIterator* next = iter.next(); + if (iter.done()) { + return next->objectBeingIterated() == obj; + } + + return true; +} + +#endif /* vm_Compartment_inl_h */ |