michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifndef jscntxtinlines_h michael@0: #define jscntxtinlines_h michael@0: michael@0: #include "jscntxt.h" michael@0: #include "jscompartment.h" michael@0: michael@0: #include "jsiter.h" michael@0: #include "jsworkers.h" michael@0: michael@0: #include "builtin/Object.h" michael@0: #include "jit/IonFrames.h" michael@0: #include "vm/ForkJoin.h" michael@0: #include "vm/Interpreter.h" michael@0: #include "vm/ProxyObject.h" michael@0: michael@0: namespace js { michael@0: michael@0: #ifdef JS_CRASH_DIAGNOSTICS michael@0: class CompartmentChecker michael@0: { michael@0: JSCompartment *compartment; michael@0: michael@0: public: michael@0: explicit CompartmentChecker(ExclusiveContext *cx) michael@0: : compartment(cx->compartment()) michael@0: { michael@0: #ifdef DEBUG michael@0: // In debug builds, make sure the embedder passed the cx it claimed it michael@0: // was going to use. michael@0: JSContext *activeContext = nullptr; michael@0: if (cx->isJSContext()) michael@0: activeContext = cx->asJSContext()->runtime()->activeContext; michael@0: JS_ASSERT_IF(activeContext, cx == activeContext); michael@0: #endif michael@0: } michael@0: michael@0: /* michael@0: * Set a breakpoint here (break js::CompartmentChecker::fail) to debug michael@0: * compartment mismatches. michael@0: */ michael@0: static void fail(JSCompartment *c1, JSCompartment *c2) { michael@0: printf("*** Compartment mismatch %p vs. %p\n", (void *) c1, (void *) c2); michael@0: MOZ_CRASH(); michael@0: } michael@0: michael@0: static void fail(JS::Zone *z1, JS::Zone *z2) { michael@0: printf("*** Zone mismatch %p vs. %p\n", (void *) z1, (void *) z2); michael@0: MOZ_CRASH(); michael@0: } michael@0: michael@0: /* Note: should only be used when neither c1 nor c2 may be the atoms compartment. */ michael@0: static void check(JSCompartment *c1, JSCompartment *c2) { michael@0: JS_ASSERT(!c1->runtimeFromAnyThread()->isAtomsCompartment(c1)); michael@0: JS_ASSERT(!c2->runtimeFromAnyThread()->isAtomsCompartment(c2)); michael@0: if (c1 != c2) michael@0: fail(c1, c2); michael@0: } michael@0: michael@0: void check(JSCompartment *c) { michael@0: if (c && !compartment->runtimeFromAnyThread()->isAtomsCompartment(c)) { michael@0: if (!compartment) michael@0: compartment = c; michael@0: else if (c != compartment) michael@0: fail(compartment, c); michael@0: } michael@0: } michael@0: michael@0: void checkZone(JS::Zone *z) { michael@0: if (compartment && z != compartment->zone()) michael@0: fail(compartment->zone(), z); michael@0: } michael@0: michael@0: void check(JSObject *obj) { michael@0: if (obj) michael@0: check(obj->compartment()); michael@0: } michael@0: michael@0: template michael@0: void check(const Rooted& rooted) { michael@0: check(rooted.get()); michael@0: } michael@0: michael@0: template michael@0: void check(Handle handle) { michael@0: check(handle.get()); michael@0: } michael@0: michael@0: void check(JSString *str) { michael@0: if (!str->isAtom()) michael@0: checkZone(str->zone()); michael@0: } michael@0: michael@0: void check(const js::Value &v) { michael@0: if (v.isObject()) michael@0: check(&v.toObject()); michael@0: else if (v.isString()) michael@0: check(v.toString()); michael@0: } michael@0: michael@0: void check(const ValueArray &arr) { michael@0: for (size_t i = 0; i < arr.length; i++) michael@0: check(arr.array[i]); michael@0: } michael@0: michael@0: void check(const JSValueArray &arr) { michael@0: for (size_t i = 0; i < arr.length; i++) michael@0: check(arr.array[i]); michael@0: } michael@0: michael@0: void check(const JS::HandleValueArray &arr) { michael@0: for (size_t i = 0; i < arr.length(); i++) michael@0: check(arr[i]); michael@0: } michael@0: michael@0: void check(const CallArgs &args) { michael@0: for (Value *p = args.base(); p != args.end(); ++p) michael@0: check(*p); michael@0: } michael@0: michael@0: void check(jsid id) { michael@0: if (JSID_IS_OBJECT(id)) michael@0: check(JSID_TO_OBJECT(id)); michael@0: } michael@0: michael@0: void check(JSIdArray *ida) { michael@0: if (ida) { michael@0: for (int i = 0; i < ida->length; i++) { michael@0: if (JSID_IS_OBJECT(ida->vector[i])) michael@0: check(ida->vector[i]); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void check(JSScript *script) { michael@0: if (script) michael@0: check(script->compartment()); michael@0: } michael@0: michael@0: void check(InterpreterFrame *fp); michael@0: void check(AbstractFramePtr frame); michael@0: }; michael@0: #endif /* JS_CRASH_DIAGNOSTICS */ michael@0: michael@0: /* michael@0: * Don't perform these checks when called from a finalizer. The checking michael@0: * depends on other objects not having been swept yet. michael@0: */ michael@0: #define START_ASSERT_SAME_COMPARTMENT() \ michael@0: if (!cx->isExclusiveContext()) \ michael@0: return; \ michael@0: if (cx->isJSContext() && cx->asJSContext()->runtime()->isHeapBusy()) \ michael@0: return; \ michael@0: CompartmentChecker c(cx->asExclusiveContext()) michael@0: michael@0: template inline void michael@0: assertSameCompartment(ThreadSafeContext *cx, const T1 &t1) michael@0: { michael@0: #ifdef JS_CRASH_DIAGNOSTICS michael@0: START_ASSERT_SAME_COMPARTMENT(); michael@0: c.check(t1); michael@0: #endif michael@0: } michael@0: michael@0: template inline void michael@0: assertSameCompartmentDebugOnly(ThreadSafeContext *cx, const T1 &t1) michael@0: { michael@0: #ifdef DEBUG michael@0: START_ASSERT_SAME_COMPARTMENT(); michael@0: c.check(t1); michael@0: #endif michael@0: } michael@0: michael@0: template inline void michael@0: assertSameCompartment(ThreadSafeContext *cx, const T1 &t1, const T2 &t2) michael@0: { michael@0: #ifdef JS_CRASH_DIAGNOSTICS michael@0: START_ASSERT_SAME_COMPARTMENT(); michael@0: c.check(t1); michael@0: c.check(t2); michael@0: #endif michael@0: } michael@0: michael@0: template inline void michael@0: assertSameCompartment(ThreadSafeContext *cx, const T1 &t1, const T2 &t2, const T3 &t3) michael@0: { michael@0: #ifdef JS_CRASH_DIAGNOSTICS michael@0: START_ASSERT_SAME_COMPARTMENT(); michael@0: c.check(t1); michael@0: c.check(t2); michael@0: c.check(t3); michael@0: #endif michael@0: } michael@0: michael@0: template inline void michael@0: assertSameCompartment(ThreadSafeContext *cx, michael@0: const T1 &t1, const T2 &t2, const T3 &t3, const T4 &t4) michael@0: { michael@0: #ifdef JS_CRASH_DIAGNOSTICS michael@0: START_ASSERT_SAME_COMPARTMENT(); michael@0: c.check(t1); michael@0: c.check(t2); michael@0: c.check(t3); michael@0: c.check(t4); michael@0: #endif michael@0: } michael@0: michael@0: template inline void michael@0: assertSameCompartment(ThreadSafeContext *cx, michael@0: const T1 &t1, const T2 &t2, const T3 &t3, const T4 &t4, const T5 &t5) michael@0: { michael@0: #ifdef JS_CRASH_DIAGNOSTICS michael@0: START_ASSERT_SAME_COMPARTMENT(); michael@0: c.check(t1); michael@0: c.check(t2); michael@0: c.check(t3); michael@0: c.check(t4); michael@0: c.check(t5); michael@0: #endif michael@0: } michael@0: michael@0: #undef START_ASSERT_SAME_COMPARTMENT michael@0: michael@0: STATIC_PRECONDITION_ASSUME(ubound(args.argv_) >= argc) michael@0: MOZ_ALWAYS_INLINE bool michael@0: CallJSNative(JSContext *cx, Native native, const CallArgs &args) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: michael@0: #ifdef DEBUG michael@0: bool alreadyThrowing = cx->isExceptionPending(); michael@0: #endif michael@0: assertSameCompartment(cx, args); michael@0: bool ok = native(cx, args.length(), args.base()); michael@0: if (ok) { michael@0: assertSameCompartment(cx, args.rval()); michael@0: JS_ASSERT_IF(!alreadyThrowing, !cx->isExceptionPending()); michael@0: } michael@0: return ok; michael@0: } michael@0: michael@0: STATIC_PRECONDITION_ASSUME(ubound(args.argv_) >= argc) michael@0: MOZ_ALWAYS_INLINE bool michael@0: CallNativeImpl(JSContext *cx, NativeImpl impl, const CallArgs &args) michael@0: { michael@0: #ifdef DEBUG michael@0: bool alreadyThrowing = cx->isExceptionPending(); michael@0: #endif michael@0: assertSameCompartment(cx, args); michael@0: bool ok = impl(cx, args); michael@0: if (ok) { michael@0: assertSameCompartment(cx, args.rval()); michael@0: JS_ASSERT_IF(!alreadyThrowing, !cx->isExceptionPending()); michael@0: } michael@0: return ok; michael@0: } michael@0: michael@0: STATIC_PRECONDITION(ubound(args.argv_) >= argc) michael@0: MOZ_ALWAYS_INLINE bool michael@0: CallJSNativeConstructor(JSContext *cx, Native native, const CallArgs &args) michael@0: { michael@0: #ifdef DEBUG michael@0: RootedObject callee(cx, &args.callee()); michael@0: #endif michael@0: michael@0: JS_ASSERT(args.thisv().isMagic()); michael@0: if (!CallJSNative(cx, native, args)) michael@0: return false; michael@0: michael@0: /* michael@0: * Native constructors must return non-primitive values on success. michael@0: * Although it is legal, if a constructor returns the callee, there is a michael@0: * 99.9999% chance it is a bug. If any valid code actually wants the michael@0: * constructor to return the callee, the assertion can be removed or michael@0: * (another) conjunct can be added to the antecedent. michael@0: * michael@0: * Exceptions: michael@0: * michael@0: * - Proxies are exceptions to both rules: they can return primitives and michael@0: * they allow content to return the callee. michael@0: * michael@0: * - CallOrConstructBoundFunction is an exception as well because we might michael@0: * have used bind on a proxy function. michael@0: * michael@0: * - new Iterator(x) is user-hookable; it returns x.__iterator__() which michael@0: * could be any object. michael@0: * michael@0: * - (new Object(Object)) returns the callee. michael@0: */ michael@0: JS_ASSERT_IF(native != ProxyObject::callableClass_.construct && michael@0: native != js::CallOrConstructBoundFunction && michael@0: native != js::IteratorConstructor && michael@0: (!callee->is() || callee->as().native() != obj_construct), michael@0: !args.rval().isPrimitive() && callee != &args.rval().toObject()); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: CallJSPropertyOp(JSContext *cx, PropertyOp op, HandleObject receiver, HandleId id, MutableHandleValue vp) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: michael@0: assertSameCompartment(cx, receiver, id, vp); michael@0: bool ok = op(cx, receiver, id, vp); michael@0: if (ok) michael@0: assertSameCompartment(cx, vp); michael@0: return ok; michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: CallJSPropertyOpSetter(JSContext *cx, StrictPropertyOp op, HandleObject obj, HandleId id, michael@0: bool strict, MutableHandleValue vp) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: michael@0: assertSameCompartment(cx, obj, id, vp); michael@0: return op(cx, obj, id, strict, vp); michael@0: } michael@0: michael@0: static inline bool michael@0: CallJSDeletePropertyOp(JSContext *cx, JSDeletePropertyOp op, HandleObject receiver, HandleId id, michael@0: bool *succeeded) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: michael@0: assertSameCompartment(cx, receiver, id); michael@0: return op(cx, receiver, id, succeeded); michael@0: } michael@0: michael@0: inline bool michael@0: CallSetter(JSContext *cx, HandleObject obj, HandleId id, StrictPropertyOp op, unsigned attrs, michael@0: bool strict, MutableHandleValue vp) michael@0: { michael@0: if (attrs & JSPROP_SETTER) { michael@0: RootedValue opv(cx, CastAsObjectJsval(op)); michael@0: return InvokeGetterOrSetter(cx, obj, opv, 1, vp.address(), vp); michael@0: } michael@0: michael@0: if (attrs & JSPROP_GETTER) michael@0: return js_ReportGetterOnlyAssignment(cx, strict); michael@0: michael@0: return CallJSPropertyOpSetter(cx, op, obj, id, strict, vp); michael@0: } michael@0: michael@0: inline uintptr_t michael@0: GetNativeStackLimit(ThreadSafeContext *cx) michael@0: { michael@0: StackKind kind; michael@0: if (cx->isJSContext()) { michael@0: kind = cx->asJSContext()->runningWithTrustedPrincipals() michael@0: ? StackForTrustedScript : StackForUntrustedScript; michael@0: } else { michael@0: // For other threads, we just use the trusted stack depth, since it's michael@0: // unlikely that we'll be mixing trusted and untrusted code together. michael@0: kind = StackForTrustedScript; michael@0: } michael@0: return cx->perThreadData->nativeStackLimit[kind]; michael@0: } michael@0: michael@0: inline LifoAlloc & michael@0: ExclusiveContext::typeLifoAlloc() michael@0: { michael@0: return zone()->types.typeLifoAlloc; michael@0: } michael@0: michael@0: } /* namespace js */ michael@0: michael@0: inline void michael@0: JSContext::setPendingException(js::Value v) michael@0: { michael@0: JS_ASSERT(!IsPoisonedValue(v)); michael@0: this->throwing = true; michael@0: this->unwrappedException_ = v; michael@0: // We don't use assertSameCompartment here to allow michael@0: // js::SetPendingExceptionCrossContext to work. michael@0: JS_ASSERT_IF(v.isObject(), v.toObject().compartment() == compartment()); michael@0: } michael@0: michael@0: inline void michael@0: JSContext::setDefaultCompartmentObject(JSObject *obj) michael@0: { michael@0: JS_ASSERT(!options().noDefaultCompartmentObject()); michael@0: defaultCompartmentObject_ = obj; michael@0: } michael@0: michael@0: inline void michael@0: JSContext::setDefaultCompartmentObjectIfUnset(JSObject *obj) michael@0: { michael@0: if (!options().noDefaultCompartmentObject() && michael@0: !defaultCompartmentObject_) michael@0: { michael@0: setDefaultCompartmentObject(obj); michael@0: } michael@0: } michael@0: michael@0: inline void michael@0: js::ExclusiveContext::enterCompartment(JSCompartment *c) michael@0: { michael@0: enterCompartmentDepth_++; michael@0: c->enter(); michael@0: setCompartment(c); michael@0: } michael@0: michael@0: inline void michael@0: js::ExclusiveContext::enterNullCompartment() michael@0: { michael@0: enterCompartmentDepth_++; michael@0: setCompartment(nullptr); michael@0: } michael@0: michael@0: inline void michael@0: js::ExclusiveContext::leaveCompartment(JSCompartment *oldCompartment) michael@0: { michael@0: JS_ASSERT(hasEnteredCompartment()); michael@0: enterCompartmentDepth_--; michael@0: michael@0: // Only call leave() after we've setCompartment()-ed away from the current michael@0: // compartment. michael@0: JSCompartment *startingCompartment = compartment_; michael@0: setCompartment(oldCompartment); michael@0: if (startingCompartment) michael@0: startingCompartment->leave(); michael@0: } michael@0: michael@0: inline void michael@0: js::ExclusiveContext::setCompartment(JSCompartment *comp) michael@0: { michael@0: // ExclusiveContexts can only be in the atoms zone or in exclusive zones. michael@0: JS_ASSERT_IF(!isJSContext() && !runtime_->isAtomsCompartment(comp), michael@0: comp->zone()->usedByExclusiveThread); michael@0: michael@0: // Normal JSContexts cannot enter exclusive zones. michael@0: JS_ASSERT_IF(isJSContext() && comp, michael@0: !comp->zone()->usedByExclusiveThread); michael@0: michael@0: // Only one thread can be in the atoms compartment at a time. michael@0: JS_ASSERT_IF(runtime_->isAtomsCompartment(comp), michael@0: runtime_->currentThreadHasExclusiveAccess()); michael@0: michael@0: // Make sure that the atoms compartment has its own zone. michael@0: JS_ASSERT_IF(comp && !runtime_->isAtomsCompartment(comp), michael@0: !runtime_->isAtomsZone(comp->zone())); michael@0: michael@0: // Both the current and the new compartment should be properly marked as michael@0: // entered at this point. michael@0: JS_ASSERT_IF(compartment_, compartment_->hasBeenEntered()); michael@0: JS_ASSERT_IF(comp, comp->hasBeenEntered()); michael@0: michael@0: compartment_ = comp; michael@0: zone_ = comp ? comp->zone() : nullptr; michael@0: allocator_ = zone_ ? &zone_->allocator : nullptr; michael@0: } michael@0: michael@0: inline JSScript * michael@0: JSContext::currentScript(jsbytecode **ppc, michael@0: MaybeAllowCrossCompartment allowCrossCompartment) const michael@0: { michael@0: if (ppc) michael@0: *ppc = nullptr; michael@0: michael@0: js::Activation *act = mainThread().activation(); michael@0: while (act && (act->cx() != this || (act->isJit() && !act->asJit()->isActive()))) michael@0: act = act->prev(); michael@0: michael@0: if (!act) michael@0: return nullptr; michael@0: michael@0: JS_ASSERT(act->cx() == this); michael@0: michael@0: #ifdef JS_ION michael@0: if (act->isJit()) { michael@0: JSScript *script = nullptr; michael@0: js::jit::GetPcScript(const_cast(this), &script, ppc); michael@0: if (!allowCrossCompartment && script->compartment() != compartment()) michael@0: return nullptr; michael@0: return script; michael@0: } michael@0: michael@0: if (act->isAsmJS()) michael@0: return nullptr; michael@0: #endif michael@0: michael@0: JS_ASSERT(act->isInterpreter()); michael@0: michael@0: js::InterpreterFrame *fp = act->asInterpreter()->current(); michael@0: JS_ASSERT(!fp->runningInJit()); michael@0: michael@0: JSScript *script = fp->script(); michael@0: if (!allowCrossCompartment && script->compartment() != compartment()) michael@0: return nullptr; michael@0: michael@0: if (ppc) { michael@0: *ppc = act->asInterpreter()->regs().pc; michael@0: JS_ASSERT(script->containsPC(*ppc)); michael@0: } michael@0: return script; michael@0: } michael@0: michael@0: template michael@0: inline bool michael@0: JSNativeThreadSafeWrapper(JSContext *cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: return threadSafeNative(cx, argc, vp); michael@0: } michael@0: michael@0: template michael@0: inline bool michael@0: JSParallelNativeThreadSafeWrapper(js::ForkJoinContext *cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: return threadSafeNative(cx, argc, vp); michael@0: } michael@0: michael@0: /* static */ inline JSContext * michael@0: js::ExecutionModeTraits::toContextType(ExclusiveContext *cx) michael@0: { michael@0: return cx->asJSContext(); michael@0: } michael@0: michael@0: #endif /* jscntxtinlines_h */