diff -r 000000000000 -r 6474c204b198 js/src/jscntxtinlines.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/src/jscntxtinlines.h Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,525 @@ +/* -*- 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 "jsworkers.h" + +#include "builtin/Object.h" +#include "jit/IonFrames.h" +#include "vm/ForkJoin.h" +#include "vm/Interpreter.h" +#include "vm/ProxyObject.h" + +namespace js { + +#ifdef JS_CRASH_DIAGNOSTICS +class CompartmentChecker +{ + JSCompartment *compartment; + + public: + explicit CompartmentChecker(ExclusiveContext *cx) + : compartment(cx->compartment()) + { +#ifdef DEBUG + // In debug builds, make sure the embedder passed the cx it claimed it + // was going to use. + JSContext *activeContext = nullptr; + if (cx->isJSContext()) + activeContext = cx->asJSContext()->runtime()->activeContext; + JS_ASSERT_IF(activeContext, cx == activeContext); +#endif + } + + /* + * 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) { + JS_ASSERT(!c1->runtimeFromAnyThread()->isAtomsCompartment(c1)); + JS_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) { + 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) { + 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) { + if (JSID_IS_OBJECT(id)) + check(JSID_TO_OBJECT(id)); + } + + void check(JSIdArray *ida) { + if (ida) { + for (int i = 0; i < ida->length; i++) { + if (JSID_IS_OBJECT(ida->vector[i])) + check(ida->vector[i]); + } + } + } + + void check(JSScript *script) { + if (script) + check(script->compartment()); + } + + void check(InterpreterFrame *fp); + void check(AbstractFramePtr frame); +}; +#endif /* JS_CRASH_DIAGNOSTICS */ + +/* + * 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->isExclusiveContext()) \ + return; \ + if (cx->isJSContext() && cx->asJSContext()->runtime()->isHeapBusy()) \ + return; \ + CompartmentChecker c(cx->asExclusiveContext()) + +template inline void +assertSameCompartment(ThreadSafeContext *cx, const T1 &t1) +{ +#ifdef JS_CRASH_DIAGNOSTICS + START_ASSERT_SAME_COMPARTMENT(); + c.check(t1); +#endif +} + +template inline void +assertSameCompartmentDebugOnly(ThreadSafeContext *cx, const T1 &t1) +{ +#ifdef DEBUG + START_ASSERT_SAME_COMPARTMENT(); + c.check(t1); +#endif +} + +template inline void +assertSameCompartment(ThreadSafeContext *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(ThreadSafeContext *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(ThreadSafeContext *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(ThreadSafeContext *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()); + JS_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()); + JS_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 + + JS_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. + */ + JS_ASSERT_IF(native != ProxyObject::callableClass_.construct && + native != js::CallOrConstructBoundFunction && + native != js::IteratorConstructor && + (!callee->is() || callee->as().native() != obj_construct), + !args.rval().isPrimitive() && callee != &args.rval().toObject()); + + return true; +} + +MOZ_ALWAYS_INLINE bool +CallJSPropertyOp(JSContext *cx, PropertyOp op, HandleObject receiver, HandleId id, MutableHandleValue vp) +{ + JS_CHECK_RECURSION(cx, return false); + + assertSameCompartment(cx, receiver, id, vp); + bool ok = op(cx, receiver, id, vp); + if (ok) + assertSameCompartment(cx, vp); + return ok; +} + +MOZ_ALWAYS_INLINE bool +CallJSPropertyOpSetter(JSContext *cx, StrictPropertyOp op, HandleObject obj, HandleId id, + bool strict, MutableHandleValue vp) +{ + JS_CHECK_RECURSION(cx, return false); + + assertSameCompartment(cx, obj, id, vp); + return op(cx, obj, id, strict, vp); +} + +static inline bool +CallJSDeletePropertyOp(JSContext *cx, JSDeletePropertyOp op, HandleObject receiver, HandleId id, + bool *succeeded) +{ + JS_CHECK_RECURSION(cx, return false); + + assertSameCompartment(cx, receiver, id); + return op(cx, receiver, id, succeeded); +} + +inline bool +CallSetter(JSContext *cx, HandleObject obj, HandleId id, StrictPropertyOp op, unsigned attrs, + bool strict, MutableHandleValue vp) +{ + if (attrs & JSPROP_SETTER) { + RootedValue opv(cx, CastAsObjectJsval(op)); + return InvokeGetterOrSetter(cx, obj, opv, 1, vp.address(), vp); + } + + if (attrs & JSPROP_GETTER) + return js_ReportGetterOnlyAssignment(cx, strict); + + return CallJSPropertyOpSetter(cx, op, obj, id, strict, vp); +} + +inline uintptr_t +GetNativeStackLimit(ThreadSafeContext *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->perThreadData->nativeStackLimit[kind]; +} + +inline LifoAlloc & +ExclusiveContext::typeLifoAlloc() +{ + return zone()->types.typeLifoAlloc; +} + +} /* namespace js */ + +inline void +JSContext::setPendingException(js::Value v) +{ + JS_ASSERT(!IsPoisonedValue(v)); + this->throwing = true; + this->unwrappedException_ = v; + // We don't use assertSameCompartment here to allow + // js::SetPendingExceptionCrossContext to work. + JS_ASSERT_IF(v.isObject(), v.toObject().compartment() == compartment()); +} + +inline void +JSContext::setDefaultCompartmentObject(JSObject *obj) +{ + JS_ASSERT(!options().noDefaultCompartmentObject()); + defaultCompartmentObject_ = obj; +} + +inline void +JSContext::setDefaultCompartmentObjectIfUnset(JSObject *obj) +{ + if (!options().noDefaultCompartmentObject() && + !defaultCompartmentObject_) + { + setDefaultCompartmentObject(obj); + } +} + +inline void +js::ExclusiveContext::enterCompartment(JSCompartment *c) +{ + enterCompartmentDepth_++; + c->enter(); + setCompartment(c); +} + +inline void +js::ExclusiveContext::enterNullCompartment() +{ + enterCompartmentDepth_++; + setCompartment(nullptr); +} + +inline void +js::ExclusiveContext::leaveCompartment(JSCompartment *oldCompartment) +{ + JS_ASSERT(hasEnteredCompartment()); + enterCompartmentDepth_--; + + // Only call leave() after we've setCompartment()-ed away from the current + // compartment. + JSCompartment *startingCompartment = compartment_; + setCompartment(oldCompartment); + if (startingCompartment) + startingCompartment->leave(); +} + +inline void +js::ExclusiveContext::setCompartment(JSCompartment *comp) +{ + // ExclusiveContexts can only be in the atoms zone or in exclusive zones. + JS_ASSERT_IF(!isJSContext() && !runtime_->isAtomsCompartment(comp), + comp->zone()->usedByExclusiveThread); + + // Normal JSContexts cannot enter exclusive zones. + JS_ASSERT_IF(isJSContext() && comp, + !comp->zone()->usedByExclusiveThread); + + // Only one thread can be in the atoms compartment at a time. + JS_ASSERT_IF(runtime_->isAtomsCompartment(comp), + runtime_->currentThreadHasExclusiveAccess()); + + // Make sure that the atoms compartment has its own zone. + JS_ASSERT_IF(comp && !runtime_->isAtomsCompartment(comp), + !runtime_->isAtomsZone(comp->zone())); + + // Both the current and the new compartment should be properly marked as + // entered at this point. + JS_ASSERT_IF(compartment_, compartment_->hasBeenEntered()); + JS_ASSERT_IF(comp, comp->hasBeenEntered()); + + compartment_ = comp; + zone_ = comp ? comp->zone() : nullptr; + allocator_ = zone_ ? &zone_->allocator : nullptr; +} + +inline JSScript * +JSContext::currentScript(jsbytecode **ppc, + MaybeAllowCrossCompartment allowCrossCompartment) const +{ + if (ppc) + *ppc = nullptr; + + js::Activation *act = mainThread().activation(); + while (act && (act->cx() != this || (act->isJit() && !act->asJit()->isActive()))) + act = act->prev(); + + if (!act) + return nullptr; + + JS_ASSERT(act->cx() == this); + +#ifdef JS_ION + if (act->isJit()) { + JSScript *script = nullptr; + js::jit::GetPcScript(const_cast(this), &script, ppc); + if (!allowCrossCompartment && script->compartment() != compartment()) + return nullptr; + return script; + } + + if (act->isAsmJS()) + return nullptr; +#endif + + JS_ASSERT(act->isInterpreter()); + + js::InterpreterFrame *fp = act->asInterpreter()->current(); + JS_ASSERT(!fp->runningInJit()); + + JSScript *script = fp->script(); + if (!allowCrossCompartment && script->compartment() != compartment()) + return nullptr; + + if (ppc) { + *ppc = act->asInterpreter()->regs().pc; + JS_ASSERT(script->containsPC(*ppc)); + } + return script; +} + +template +inline bool +JSNativeThreadSafeWrapper(JSContext *cx, unsigned argc, JS::Value *vp) +{ + return threadSafeNative(cx, argc, vp); +} + +template +inline bool +JSParallelNativeThreadSafeWrapper(js::ForkJoinContext *cx, unsigned argc, JS::Value *vp) +{ + return threadSafeNative(cx, argc, vp); +} + +/* static */ inline JSContext * +js::ExecutionModeTraits::toContextType(ExclusiveContext *cx) +{ + return cx->asJSContext(); +} + +#endif /* jscntxtinlines_h */