/* -*- 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 #include #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 void checkAtom(T* thing, int argIndex) { static_assert(std::is_same_v || std::is_same_v, "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. template std::enable_if_t().begin()), decltype(std::declval().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 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> maybe, int argIndex) { if (maybe.get().isSome()) { check(maybe.get().ref(), argIndex); } } }; } // namespace js template inline void JSContext::checkImpl(const Args&... args) { int argIndex = 0; (..., js::ContextChecks(this).check(args, argIndex++)); } template inline void JSContext::check(const Args&... args) { #ifdef JS_CRASH_DIAGNOSTICS if (contextChecksEnabled()) { checkImpl(args...); } #endif } template inline void JSContext::releaseCheck(const Args&... args) { if (contextChecksEnabled()) { checkImpl(args...); } } template 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(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 */