Source code

Revision control

Other Tools

/* -*- 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 */