diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /js/src/vm/JSContext-inl.h | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/vm/JSContext-inl.h')
-rw-r--r-- | js/src/vm/JSContext-inl.h | 474 |
1 files changed, 474 insertions, 0 deletions
diff --git a/js/src/vm/JSContext-inl.h b/js/src/vm/JSContext-inl.h new file mode 100644 index 0000000000..b578b169b4 --- /dev/null +++ b/js/src/vm/JSContext-inl.h @@ -0,0 +1,474 @@ +/* -*- 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_JSContext_inl_h +#define vm_JSContext_inl_h + +#include "vm/JSContext.h" + +#include <type_traits> +#include <utility> + +#include "builtin/Object.h" +#include "gc/Zone.h" +#include "jit/JitFrames.h" +#include "js/friend/StackLimits.h" // js::CheckRecursionLimit +#include "proxy/Proxy.h" +#include "util/DiagnosticAssertions.h" +#include "vm/BigIntType.h" +#include "vm/GlobalObject.h" +#include "vm/HelperThreads.h" +#include "vm/Interpreter.h" +#include "vm/Iteration.h" +#include "vm/Realm.h" +#include "vm/SymbolType.h" + +#include "vm/Activation-inl.h" // js::Activation::hasWasmExitFP + +namespace js { + +class ContextChecks { + JSContext* cx; + + JS::Realm* realm() const { return cx->realm(); } + JS::Compartment* compartment() const { return cx->compartment(); } + JS::Zone* zone() const { return cx->zone(); } + + public: + explicit ContextChecks(JSContext* cx) : cx(cx) { +#ifdef DEBUG + if (realm()) { + GlobalObject* global = realm()->unsafeUnbarrieredMaybeGlobal(); + if (global) { + checkObject(global); + } + } +#endif + } + + /* + * Set a breakpoint here (break js::ContextChecks::fail) to debug + * realm/compartment/zone mismatches. + */ + static void fail(JS::Realm* r1, JS::Realm* r2, int argIndex) { + MOZ_CRASH_UNSAFE_PRINTF("*** Realm mismatch %p vs. %p at argument %d", r1, + r2, argIndex); + } + static void fail(JS::Compartment* c1, JS::Compartment* c2, int argIndex) { + MOZ_CRASH_UNSAFE_PRINTF("*** Compartment mismatch %p vs. %p at argument %d", + c1, c2, argIndex); + } + static void fail(JS::Zone* z1, JS::Zone* z2, int argIndex) { + MOZ_CRASH_UNSAFE_PRINTF("*** Zone mismatch %p vs. %p at argument %d", z1, + z2, argIndex); + } + + void check(JS::Realm* r, int argIndex) { + if (r && r != realm()) { + fail(realm(), r, argIndex); + } + } + + void check(JS::Compartment* c, int argIndex) { + if (c && c != compartment()) { + fail(compartment(), c, argIndex); + } + } + + void check(JS::Zone* z, int argIndex) { + if (zone() && z != zone()) { + fail(zone(), z, argIndex); + } + } + + void check(JSObject* obj, int argIndex) { + if (obj) { + checkObject(obj); + check(obj->compartment(), argIndex); + } + } + + void checkObject(JSObject* obj) { + JS::AssertObjectIsNotGray(obj); + MOZ_ASSERT(!js::gc::IsAboutToBeFinalizedUnbarriered(&obj)); + } + + template <typename T> + void checkAtom(T* thing, int argIndex) { + static_assert(std::is_same_v<T, JSAtom> || std::is_same_v<T, JS::Symbol>, + "Should only be called with JSAtom* or JS::Symbol* argument"); + +#ifdef DEBUG + // Atoms which move across zone boundaries need to be marked in the new + // zone, see JS_MarkCrossZoneId. + if (zone()) { + if (!cx->runtime()->gc.atomMarking.atomIsMarked(zone(), thing)) { + MOZ_CRASH_UNSAFE_PRINTF( + "*** Atom not marked for zone %p at argument %d", zone(), argIndex); + } + } +#endif + } + + void check(JSString* str, int argIndex) { + JS::AssertCellIsNotGray(str); + if (str->isAtom()) { + checkAtom(&str->asAtom(), argIndex); + } else { + check(str->zone(), argIndex); + } + } + + void check(JS::Symbol* symbol, int argIndex) { checkAtom(symbol, argIndex); } + + void check(JS::BigInt* bi, int argIndex) { check(bi->zone(), argIndex); } + + void check(const js::Value& v, int argIndex) { + if (v.isObject()) { + check(&v.toObject(), argIndex); + } else if (v.isString()) { + check(v.toString(), argIndex); + } else if (v.isSymbol()) { + check(v.toSymbol(), argIndex); + } else if (v.isBigInt()) { + check(v.toBigInt(), argIndex); + } + } + + // Check the contents of any container class that supports the C++ + // iteration protocol, eg GCVector<jsid>. + template <typename Container> + std::enable_if_t<std::is_same_v<decltype(std::declval<Container>().begin()), + decltype(std::declval<Container>().end())>> + check(const Container& container, int argIndex) { + for (auto i : container) { + check(i, argIndex); + } + } + + void check(const JS::HandleValueArray& arr, int argIndex) { + for (size_t i = 0; i < arr.length(); i++) { + check(arr[i], argIndex); + } + } + + void check(const CallArgs& args, int argIndex) { + for (Value* p = args.base(); p != args.end(); ++p) { + check(*p, argIndex); + } + } + + void check(jsid id, int argIndex) { + if (JSID_IS_ATOM(id)) { + checkAtom(JSID_TO_ATOM(id), argIndex); + } else if (JSID_IS_SYMBOL(id)) { + checkAtom(JSID_TO_SYMBOL(id), argIndex); + } else { + MOZ_ASSERT(!id.isGCThing()); + } + } + + void check(JSScript* script, int argIndex) { + JS::AssertCellIsNotGray(script); + if (script) { + check(script->realm(), argIndex); + } + } + + void check(AbstractFramePtr frame, int argIndex); + + void check(Handle<PropertyDescriptor> desc, int argIndex) { + check(desc.object(), argIndex); + if (desc.hasGetterObject()) { + check(desc.getterObject(), argIndex); + } + if (desc.hasSetterObject()) { + check(desc.setterObject(), argIndex); + } + check(desc.value(), argIndex); + } + + void check(JS::Handle<mozilla::Maybe<JS::Value>> maybe, int argIndex) { + if (maybe.get().isSome()) { + check(maybe.get().ref(), argIndex); + } + } +}; + +} // namespace js + +template <class... Args> +inline void JSContext::checkImpl(const Args&... args) { + int argIndex = 0; + (..., js::ContextChecks(this).check(args, argIndex++)); +} + +template <class... Args> +inline void JSContext::check(const Args&... args) { +#ifdef JS_CRASH_DIAGNOSTICS + if (contextChecksEnabled()) { + checkImpl(args...); + } +#endif +} + +template <class... Args> +inline void JSContext::releaseCheck(const Args&... args) { + if (contextChecksEnabled()) { + checkImpl(args...); + } +} + +template <class... Args> +MOZ_ALWAYS_INLINE void JSContext::debugOnlyCheck(const Args&... args) { +#if defined(DEBUG) && defined(JS_CRASH_DIAGNOSTICS) + if (contextChecksEnabled()) { + checkImpl(args...); + } +#endif +} + +namespace js { + +STATIC_PRECONDITION_ASSUME(ubound(args.argv_) >= argc) +MOZ_ALWAYS_INLINE bool CallNativeImpl(JSContext* cx, NativeImpl impl, + const CallArgs& args) { +#ifdef DEBUG + bool alreadyThrowing = cx->isExceptionPending(); +#endif + cx->check(args); + bool ok = impl(cx, args); + if (ok) { + cx->check(args.rval()); + MOZ_ASSERT_IF(!alreadyThrowing, !cx->isExceptionPending()); + } + return ok; +} + +MOZ_ALWAYS_INLINE bool CallJSGetterOp(JSContext* cx, GetterOp op, + HandleObject obj, HandleId id, + MutableHandleValue vp) { + if (!CheckRecursionLimit(cx)) { + return false; + } + + cx->check(obj, id, vp); + bool ok = op(cx, obj, id, vp); + if (ok) { + cx->check(vp); + } + return ok; +} + +MOZ_ALWAYS_INLINE bool CallJSSetterOp(JSContext* cx, SetterOp op, + HandleObject obj, HandleId id, + HandleValue v, ObjectOpResult& result) { + if (!CheckRecursionLimit(cx)) { + return false; + } + + cx->check(obj, id, v); + return op(cx, obj, id, v, result); +} + +inline bool CallJSAddPropertyOp(JSContext* cx, JSAddPropertyOp op, + HandleObject obj, HandleId id, HandleValue v) { + if (!CheckRecursionLimit(cx)) { + return false; + } + + cx->check(obj, id, v); + return op(cx, obj, id, v); +} + +inline bool CallJSDeletePropertyOp(JSContext* cx, JSDeletePropertyOp op, + HandleObject receiver, HandleId id, + ObjectOpResult& result) { + if (!CheckRecursionLimit(cx)) { + return false; + } + + cx->check(receiver, id); + if (op) { + return op(cx, receiver, id, result); + } + return result.succeed(); +} + +MOZ_ALWAYS_INLINE bool CheckForInterrupt(JSContext* cx) { + MOZ_ASSERT(!cx->isExceptionPending()); + // Add an inline fast-path since we have to check for interrupts in some hot + // C++ loops of library builtins. + if (MOZ_UNLIKELY(cx->hasAnyPendingInterrupt())) { + return cx->handleInterrupt(); + } + + JS_INTERRUPT_POSSIBLY_FAIL(); + + return true; +} + +} /* namespace js */ + +inline js::Nursery& JSContext::nursery() { return runtime()->gc.nursery(); } + +inline void JSContext::minorGC(JS::GCReason reason) { + runtime()->gc.minorGC(reason); +} + +inline bool JSContext::runningWithTrustedPrincipals() { + if (!realm()) { + return true; + } + if (!runtime()->trustedPrincipals()) { + return false; + } + return realm()->principals() == runtime()->trustedPrincipals(); +} + +inline void JSContext::enterRealm(JS::Realm* realm) { + // We should never enter a realm while in the atoms zone. + MOZ_ASSERT_IF(zone(), !zone()->isAtomsZone()); + + realm->enter(); + setRealm(realm); +} + +inline void JSContext::enterAtomsZone() { + realm_ = nullptr; + setZone(runtime_->unsafeAtomsZone(), AtomsZone); +} + +inline void JSContext::setZone(js::Zone* zone, + JSContext::IsAtomsZone isAtomsZone) { + if (zone_) { + zone_->addTenuredAllocsSinceMinorGC(allocsThisZoneSinceMinorGC_); + } + + allocsThisZoneSinceMinorGC_ = 0; + + zone_ = zone; + if (zone == nullptr) { + freeLists_ = nullptr; + return; + } + + if (isAtomsZone == AtomsZone && isHelperThreadContext()) { + MOZ_ASSERT(!zone_->wasGCStarted()); + freeLists_ = atomsZoneFreeLists_; + } else { + freeLists_ = &zone_->arenas.freeLists(); + } +} + +inline void JSContext::enterRealmOf(JSObject* target) { + JS::AssertCellIsNotGray(target); + enterRealm(target->nonCCWRealm()); +} + +inline void JSContext::enterRealmOf(JSScript* target) { + JS::AssertCellIsNotGray(target); + enterRealm(target->realm()); +} + +inline void JSContext::enterRealmOf(js::ObjectGroup* target) { + JS::AssertCellIsNotGray(target); + enterRealm(target->realm()); +} + +inline void JSContext::enterNullRealm() { + // We should never enter a realm while in the atoms zone. + MOZ_ASSERT_IF(zone(), !zone()->isAtomsZone()); + + setRealm(nullptr); +} + +inline void JSContext::leaveRealm(JS::Realm* oldRealm) { + // Only call leave() after we've setRealm()-ed away from the current realm. + JS::Realm* startingRealm = realm_; + + // The current realm should be marked as entered-from-C++ at this point. + MOZ_ASSERT_IF(startingRealm, startingRealm->hasBeenEnteredIgnoringJit()); + + setRealm(oldRealm); + + if (startingRealm) { + startingRealm->leave(); + } +} + +inline void JSContext::leaveAtomsZone(JS::Realm* oldRealm) { + setRealm(oldRealm); +} + +inline void JSContext::setRealm(JS::Realm* realm) { + realm_ = realm; + if (realm) { + // This thread must have exclusive access to the zone. + MOZ_ASSERT(CurrentThreadCanAccessZone(realm->zone())); + MOZ_ASSERT(!realm->zone()->isAtomsZone()); + setZone(realm->zone(), NotAtomsZone); + } else { + setZone(nullptr, NotAtomsZone); + } +} + +inline void JSContext::setRealmForJitExceptionHandler(JS::Realm* realm) { + // JIT code enters (same-compartment) realms without calling realm->enter() + // so we don't call realm->leave() here. + MOZ_ASSERT(realm->compartment() == compartment()); + realm_ = realm; +} + +inline JSScript* JSContext::currentScript( + jsbytecode** ppc, AllowCrossRealm allowCrossRealm) const { + if (ppc) { + *ppc = nullptr; + } + + js::Activation* act = activation(); + if (!act) { + return nullptr; + } + + MOZ_ASSERT(act->cx() == this); + + // Cross-compartment implies cross-realm. + if (allowCrossRealm == AllowCrossRealm::DontAllow && + act->compartment() != compartment()) { + return nullptr; + } + + JSScript* script = nullptr; + jsbytecode* pc = nullptr; + if (act->isJit()) { + if (act->hasWasmExitFP()) { + return nullptr; + } + js::jit::GetPcScript(const_cast<JSContext*>(this), &script, &pc); + } else { + js::InterpreterFrame* fp = act->asInterpreter()->current(); + MOZ_ASSERT(!fp->runningInJit()); + script = fp->script(); + pc = act->asInterpreter()->regs().pc; + } + + MOZ_ASSERT(script->containsPC(pc)); + + if (allowCrossRealm == AllowCrossRealm::DontAllow && + script->realm() != realm()) { + return nullptr; + } + + if (ppc) { + *ppc = pc; + } + return script; +} + +inline js::RuntimeCaches& JSContext::caches() { return runtime()->caches(); } + +#endif /* vm_JSContext_inl_h */ |