/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * 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 jscntxtinlines_h #define jscntxtinlines_h #include "jscntxt.h" #include "jscompartment.h" #include "jsiter.h" #include "builtin/Object.h" #include "jit/JitFrames.h" #include "vm/HelperThreads.h" #include "vm/Interpreter.h" #include "vm/ProxyObject.h" #include "vm/Symbol.h" namespace js { class CompartmentChecker { JSCompartment* compartment; public: explicit CompartmentChecker(ExclusiveContext* cx) : compartment(cx->compartment()) { } /* * Set a breakpoint here (break js::CompartmentChecker::fail) to debug * compartment mismatches. */ static void fail(JSCompartment* c1, JSCompartment* c2) { printf("*** Compartment mismatch %p vs. %p\n", (void*) c1, (void*) c2); MOZ_CRASH(); } static void fail(JS::Zone* z1, JS::Zone* z2) { printf("*** Zone mismatch %p vs. %p\n", (void*) z1, (void*) z2); MOZ_CRASH(); } /* Note: should only be used when neither c1 nor c2 may be the atoms compartment. */ static void check(JSCompartment* c1, JSCompartment* c2) { MOZ_ASSERT(!c1->runtimeFromAnyThread()->isAtomsCompartment(c1)); MOZ_ASSERT(!c2->runtimeFromAnyThread()->isAtomsCompartment(c2)); if (c1 != c2) fail(c1, c2); } void check(JSCompartment* c) { if (c && !compartment->runtimeFromAnyThread()->isAtomsCompartment(c)) { if (!compartment) compartment = c; else if (c != compartment) fail(compartment, c); } } void checkZone(JS::Zone* z) { if (compartment && z != compartment->zone()) fail(compartment->zone(), z); } void check(JSObject* obj) { MOZ_ASSERT_IF(obj, !JS::ObjectIsMarkedGray(obj)); if (obj) check(obj->compartment()); } template void check(const Rooted& rooted) { check(rooted.get()); } template void check(Handle handle) { check(handle.get()); } void check(JSString* str) { MOZ_ASSERT(!js::gc::detail::CellIsMarkedGray(str)); if (!str->isAtom()) checkZone(str->zone()); } void check(const js::Value& v) { if (v.isObject()) check(&v.toObject()); else if (v.isString()) check(v.toString()); } void check(const ValueArray& arr) { for (size_t i = 0; i < arr.length; i++) check(arr.array[i]); } void check(const JSValueArray& arr) { for (size_t i = 0; i < arr.length; i++) check(arr.array[i]); } void check(const JS::HandleValueArray& arr) { for (size_t i = 0; i < arr.length(); i++) check(arr[i]); } void check(const CallArgs& args) { for (Value* p = args.base(); p != args.end(); ++p) check(*p); } void check(jsid id) {} void check(JSScript* script) { MOZ_ASSERT_IF(script, !JS::ScriptIsMarkedGray(script)); if (script) check(script->compartment()); } void check(InterpreterFrame* fp); void check(AbstractFramePtr frame); void check(SavedStacks* stacks); void check(Handle desc) { check(desc.object()); if (desc.hasGetterObject()) check(desc.getterObject()); if (desc.hasSetterObject()) check(desc.setterObject()); check(desc.value()); } void check(TypeSet::Type type) { check(type.maybeCompartment()); } }; /* * Don't perform these checks when called from a finalizer. The checking * depends on other objects not having been swept yet. */ #define START_ASSERT_SAME_COMPARTMENT() \ if (cx->isJSContext() && cx->asJSContext()->runtime()->isHeapBusy()) \ return; \ CompartmentChecker c(cx) template inline void releaseAssertSameCompartment(ExclusiveContext* cx, const T1& t1) { START_ASSERT_SAME_COMPARTMENT(); c.check(t1); } template inline void assertSameCompartment(ExclusiveContext* cx, const T1& t1) { #ifdef JS_CRASH_DIAGNOSTICS START_ASSERT_SAME_COMPARTMENT(); c.check(t1); #endif } template inline void assertSameCompartmentDebugOnly(ExclusiveContext* cx, const T1& t1) { #if defined(DEBUG) && defined(JS_CRASH_DIAGNOSTICS) START_ASSERT_SAME_COMPARTMENT(); c.check(t1); #endif } template inline void assertSameCompartment(ExclusiveContext* cx, const T1& t1, const T2& t2) { #ifdef JS_CRASH_DIAGNOSTICS START_ASSERT_SAME_COMPARTMENT(); c.check(t1); c.check(t2); #endif } template inline void assertSameCompartment(ExclusiveContext* cx, const T1& t1, const T2& t2, const T3& t3) { #ifdef JS_CRASH_DIAGNOSTICS START_ASSERT_SAME_COMPARTMENT(); c.check(t1); c.check(t2); c.check(t3); #endif } template inline void assertSameCompartment(ExclusiveContext* cx, const T1& t1, const T2& t2, const T3& t3, const T4& t4) { #ifdef JS_CRASH_DIAGNOSTICS START_ASSERT_SAME_COMPARTMENT(); c.check(t1); c.check(t2); c.check(t3); c.check(t4); #endif } template inline void assertSameCompartment(ExclusiveContext* cx, const T1& t1, const T2& t2, const T3& t3, const T4& t4, const T5& t5) { #ifdef JS_CRASH_DIAGNOSTICS START_ASSERT_SAME_COMPARTMENT(); c.check(t1); c.check(t2); c.check(t3); c.check(t4); c.check(t5); #endif } #undef START_ASSERT_SAME_COMPARTMENT STATIC_PRECONDITION_ASSUME(ubound(args.argv_) >= argc) MOZ_ALWAYS_INLINE bool CallJSNative(JSContext* cx, Native native, const CallArgs& args) { JS_CHECK_RECURSION(cx, return false); #ifdef DEBUG bool alreadyThrowing = cx->isExceptionPending(); #endif assertSameCompartment(cx, args); bool ok = native(cx, args.length(), args.base()); if (ok) { assertSameCompartment(cx, args.rval()); MOZ_ASSERT_IF(!alreadyThrowing, !cx->isExceptionPending()); } return ok; } 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 assertSameCompartment(cx, args); bool ok = impl(cx, args); if (ok) { assertSameCompartment(cx, args.rval()); MOZ_ASSERT_IF(!alreadyThrowing, !cx->isExceptionPending()); } return ok; } STATIC_PRECONDITION(ubound(args.argv_) >= argc) MOZ_ALWAYS_INLINE bool CallJSNativeConstructor(JSContext* cx, Native native, const CallArgs& args) { #ifdef DEBUG RootedObject callee(cx, &args.callee()); #endif MOZ_ASSERT(args.thisv().isMagic()); if (!CallJSNative(cx, native, args)) return false; /* * Native constructors must return non-primitive values on success. * Although it is legal, if a constructor returns the callee, there is a * 99.9999% chance it is a bug. If any valid code actually wants the * constructor to return the callee, the assertion can be removed or * (another) conjunct can be added to the antecedent. * * Exceptions: * * - Proxies are exceptions to both rules: they can return primitives and * they allow content to return the callee. * * - CallOrConstructBoundFunction is an exception as well because we might * have used bind on a proxy function. * * - new Iterator(x) is user-hookable; it returns x.__iterator__() which * could be any object. * * - (new Object(Object)) returns the callee. */ MOZ_ASSERT_IF(native != js::proxy_Construct && native != js::IteratorConstructor && (!callee->is() || callee->as().native() != obj_construct), args.rval().isObject() && callee != &args.rval().toObject()); return true; } MOZ_ALWAYS_INLINE bool CallJSGetterOp(JSContext* cx, GetterOp op, HandleObject obj, HandleId id, MutableHandleValue vp) { JS_CHECK_RECURSION(cx, return false); assertSameCompartment(cx, obj, id, vp); bool ok = op(cx, obj, id, vp); if (ok) assertSameCompartment(cx, vp); return ok; } MOZ_ALWAYS_INLINE bool CallJSSetterOp(JSContext* cx, SetterOp op, HandleObject obj, HandleId id, MutableHandleValue vp, ObjectOpResult& result) { JS_CHECK_RECURSION(cx, return false); assertSameCompartment(cx, obj, id, vp); return op(cx, obj, id, vp, result); } inline bool CallJSAddPropertyOp(JSContext* cx, JSAddPropertyOp op, HandleObject obj, HandleId id, HandleValue v) { JS_CHECK_RECURSION(cx, return false); assertSameCompartment(cx, obj, id, v); return op(cx, obj, id, v); } inline bool CallJSDeletePropertyOp(JSContext* cx, JSDeletePropertyOp op, HandleObject receiver, HandleId id, ObjectOpResult& result) { JS_CHECK_RECURSION(cx, return false); assertSameCompartment(cx, receiver, id); if (op) return op(cx, receiver, id, result); return result.succeed(); } inline uintptr_t GetNativeStackLimit(ExclusiveContext* cx) { StackKind kind; if (cx->isJSContext()) { kind = cx->asJSContext()->runningWithTrustedPrincipals() ? StackForTrustedScript : StackForUntrustedScript; } else { // For other threads, we just use the trusted stack depth, since it's // unlikely that we'll be mixing trusted and untrusted code together. kind = StackForTrustedScript; } return cx->nativeStackLimit[kind]; } inline LifoAlloc& ExclusiveContext::typeLifoAlloc() { return zone()->types.typeLifoAlloc; } } /* namespace js */ inline void JSContext::setPendingException(JS::HandleValue v, js::HandleSavedFrame stack) { // overRecursed_ is set after the fact by ReportOverRecursed. this->overRecursed_ = false; this->throwing = true; this->unwrappedException_ = v; this->unwrappedExceptionStack_ = stack; // We don't use assertSameCompartment here to allow // js::SetPendingExceptionCrossContext to work. MOZ_ASSERT_IF(v.isObject(), v.toObject().compartment() == compartment()); } inline bool JSContext::runningWithTrustedPrincipals() const { return !compartment() || compartment()->principals() == trustedPrincipals(); } inline void js::ExclusiveContext::enterCompartment( JSCompartment* c, const js::AutoLockForExclusiveAccess* maybeLock /* = nullptr */) { enterCompartmentDepth_++; c->enter(); setCompartment(c, maybeLock); } inline void js::ExclusiveContext::enterNullCompartment() { enterCompartmentDepth_++; setCompartment(nullptr); } inline void js::ExclusiveContext::leaveCompartment( JSCompartment* oldCompartment, const js::AutoLockForExclusiveAccess* maybeLock /* = nullptr */) { MOZ_ASSERT(hasEnteredCompartment()); enterCompartmentDepth_--; // Only call leave() after we've setCompartment()-ed away from the current // compartment. JSCompartment* startingCompartment = compartment_; setCompartment(oldCompartment, maybeLock); if (startingCompartment) startingCompartment->leave(); } inline void js::ExclusiveContext::setCompartment(JSCompartment* comp, const AutoLockForExclusiveAccess* maybeLock /* = nullptr */) { // ExclusiveContexts can only be in the atoms zone or in exclusive zones. MOZ_ASSERT_IF(!isJSContext() && !runtime_->isAtomsCompartment(comp), comp->zone()->usedByExclusiveThread); // Normal JSContexts cannot enter exclusive zones. MOZ_ASSERT_IF(isJSContext() && comp, !comp->zone()->usedByExclusiveThread); // Only one thread can be in the atoms compartment at a time. MOZ_ASSERT_IF(runtime_->isAtomsCompartment(comp), maybeLock != nullptr); // Make sure that the atoms compartment has its own zone. MOZ_ASSERT_IF(comp && !runtime_->isAtomsCompartment(comp), !comp->zone()->isAtomsZone()); // Both the current and the new compartment should be properly marked as // entered at this point. MOZ_ASSERT_IF(compartment_, compartment_->hasBeenEntered()); MOZ_ASSERT_IF(comp, comp->hasBeenEntered()); compartment_ = comp; zone_ = comp ? comp->zone() : nullptr; arenas_ = zone_ ? &zone_->arenas : nullptr; } inline JSScript* JSContext::currentScript(jsbytecode** ppc, MaybeAllowCrossCompartment allowCrossCompartment) const { if (ppc) *ppc = nullptr; js::Activation* act = activation(); while (act && act->isJit() && !act->asJit()->isActive()) act = act->prev(); if (!act) return nullptr; MOZ_ASSERT(act->cx() == this); if (act->isJit()) { JSScript* script = nullptr; js::jit::GetPcScript(const_cast(this), &script, ppc); if (!allowCrossCompartment && script->compartment() != compartment()) { if (ppc) *ppc = nullptr; return nullptr; } return script; } if (act->isWasm()) return nullptr; MOZ_ASSERT(act->isInterpreter()); js::InterpreterFrame* fp = act->asInterpreter()->current(); MOZ_ASSERT(!fp->runningInJit()); JSScript* script = fp->script(); if (!allowCrossCompartment && script->compartment() != compartment()) return nullptr; if (ppc) { *ppc = act->asInterpreter()->regs().pc; MOZ_ASSERT(script->containsPC(*ppc)); } return script; } #endif /* jscntxtinlines_h */