/* -*- 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 #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> vp) { if (vp.get().isNothing()) { return true; } return wrap(cx, MutableHandle::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 const char* ClassName() { return T::class_.name; } template [[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()) { throwTypeError(); return nullptr; } return &obj->as(); } template [[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 [[nodiscard]] inline T* UnwrapAndTypeCheckValue(JSContext* cx, HandleValue value, ErrorCallback throwTypeError) { cx->check(value); static_assert(!std::is_convertible_v, "T can't be a Wrapper type; this function discards wrappers"); if (value.isObject() && value.toObject().is()) { return &value.toObject().as(); } return detail::UnwrapAndTypeCheckValueSlowPath(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 [[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 [[nodiscard]] inline T* UnwrapAndTypeCheckThis(JSContext* cx, const CallArgs& args, const char* methodName) { HandleValue thisv = args.thisv(); return UnwrapAndTypeCheckValue(cx, thisv, [cx, methodName, thisv] { JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, detail::ClassName(), 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 [[nodiscard]] inline T* UnwrapAndTypeCheckArgument(JSContext* cx, CallArgs& args, const char* methodName, int argIndex) { HandleValue val = args.get(argIndex); return UnwrapAndTypeCheckValue(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(), 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 [[nodiscard]] inline T* UnwrapAndDowncastObject(JSContext* cx, JSObject* obj) { static_assert(!std::is_convertible_v, "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(); if (!obj) { ReportAccessDenied(cx); return nullptr; } } return &obj->as(); } /** * 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 [[nodiscard]] inline T* UnwrapAndDowncastValue(JSContext* cx, const Value& value) { return UnwrapAndDowncastObject(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 [[nodiscard]] inline T* UnwrapInternalSlot(JSContext* cx, Handle unwrappedObj, uint32_t slot) { static_assert(!std::is_convertible_v, "T can't be a Wrapper type; this function discards wrappers"); return UnwrapAndDowncastValue(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 [[nodiscard]] T* UnwrapCalleeSlot(JSContext* cx, CallArgs& args, size_t extendedSlot) { JSFunction& func = args.callee().as(); return UnwrapAndDowncastValue(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 */