diff options
Diffstat (limited to 'js/src/jscntxtinlines.h')
-rw-r--r-- | js/src/jscntxtinlines.h | 497 |
1 files changed, 497 insertions, 0 deletions
diff --git a/js/src/jscntxtinlines.h b/js/src/jscntxtinlines.h new file mode 100644 index 000000000..069f34d1d --- /dev/null +++ b/js/src/jscntxtinlines.h @@ -0,0 +1,497 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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<typename T> + void check(const Rooted<T>& rooted) { + check(rooted.get()); + } + + template<typename T> + void check(Handle<T> 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<PropertyDescriptor> 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 <class T1> inline void +releaseAssertSameCompartment(ExclusiveContext* cx, const T1& t1) +{ + START_ASSERT_SAME_COMPARTMENT(); + c.check(t1); +} + +template <class T1> inline void +assertSameCompartment(ExclusiveContext* cx, const T1& t1) +{ +#ifdef JS_CRASH_DIAGNOSTICS + START_ASSERT_SAME_COMPARTMENT(); + c.check(t1); +#endif +} + +template <class T1> inline void +assertSameCompartmentDebugOnly(ExclusiveContext* cx, const T1& t1) +{ +#if defined(DEBUG) && defined(JS_CRASH_DIAGNOSTICS) + START_ASSERT_SAME_COMPARTMENT(); + c.check(t1); +#endif +} + +template <class T1, class T2> 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 <class T1, class T2, class T3> 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 <class T1, class T2, class T3, class T4> 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 <class T1, class T2, class T3, class T4, class T5> 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<JSFunction>() || callee->as<JSFunction>().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(const js::Value& v) +{ + // overRecursed_ is set after the fact by ReportOverRecursed. + this->overRecursed_ = false; + this->throwing = true; + this->unwrappedException_ = v; + // 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<JSContext*>(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 */ |