Bug 951282 - Wrap the pending exception lazily; r=luke
authorTerrence Cole <terrence@mozilla.com>
Tue, 17 Dec 2013 13:01:05 -0800
changeset 176938 caf902c1502657bafdc49c5df4fc3b7db3c81ad2
parent 176937 8500bb46251568976c4b5b53ccc4c310639bf38f
child 176939 fbeed56db62123b9ae346c9343826049b707443f
push id3343
push userffxbld
push dateMon, 17 Mar 2014 21:55:32 +0000
treeherdermozilla-beta@2f7d3415f79f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs951282
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 951282 - Wrap the pending exception lazily; r=luke
js/src/jit/IonFrames.cpp
js/src/jsapi.cpp
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/jscntxtinlines.h
js/src/jsexn.cpp
js/src/jsiter.cpp
js/src/jswrapper.cpp
js/src/vm/Debugger.cpp
js/src/vm/Interpreter.cpp
js/src/vm/OldDebugAPI.cpp
--- a/js/src/jit/IonFrames.cpp
+++ b/js/src/jit/IonFrames.cpp
@@ -535,17 +535,19 @@ HandleExceptionBaseline(JSContext *cx, c
             }
             break;
 
           case JSTRY_FINALLY:
             if (cx->isExceptionPending()) {
                 rfe->kind = ResumeFromException::RESUME_FINALLY;
                 jsbytecode *finallyPC = script->main() + tn->start + tn->length;
                 rfe->target = script->baselineScript()->nativeCodeForPC(script, finallyPC);
-                rfe->exception = cx->getPendingException();
+                // Drop the exception instead of leaking cross compartment data.
+                if (!cx->getPendingException(MutableHandleValue::fromMarkedLocation(&rfe->exception)))
+                    rfe->exception = UndefinedValue();
                 cx->clearPendingException();
                 return;
             }
             break;
 
           case JSTRY_ITER: {
             Value iterValue(* (Value *) rfe->stackPointer);
             RootedObject iterObject(cx, &iterValue.toObject());
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -5860,19 +5860,17 @@ JS_IsExceptionPending(JSContext *cx)
 
 JS_PUBLIC_API(bool)
 JS_GetPendingException(JSContext *cx, MutableHandleValue vp)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     if (!cx->isExceptionPending())
         return false;
-    vp.set(cx->getPendingException());
-    assertSameCompartment(cx, vp);
-    return true;
+    return cx->getPendingException(vp);
 }
 
 JS_PUBLIC_API(void)
 JS_SetPendingException(JSContext *cx, HandleValue value)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, value);
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -1068,17 +1068,17 @@ ThreadSafeContext::asForkJoinSlice()
 {
     JS_ASSERT(isForkJoinSlice());
     return reinterpret_cast<ForkJoinSlice *>(this);
 }
 
 JSContext::JSContext(JSRuntime *rt)
   : ExclusiveContext(rt, &rt->mainThread, Context_JS),
     throwing(false),
-    exception(UndefinedValue()),
+    unwrappedException_(UndefinedValue()),
     options_(),
     reportGranularity(JS_DEFAULT_JITREPORT_GRANULARITY),
     resolvingList(nullptr),
     generatingError(false),
     savedFrameChains_(),
     defaultCompartmentObject_(nullptr),
     cycleDetectorSet(MOZ_THIS_IN_INITIALIZER_LIST()),
     errorReporter(nullptr),
@@ -1104,31 +1104,31 @@ JSContext::JSContext(JSRuntime *rt)
 }
 
 JSContext::~JSContext()
 {
     /* Free the stuff hanging off of cx. */
     JS_ASSERT(!resolvingList);
 }
 
-/*
- * Since this function is only called in the context of a pending exception,
- * the caller must subsequently take an error path. If wrapping fails, it will
- * set a new (uncatchable) exception to be used in place of the original.
- */
-void
-JSContext::wrapPendingException()
+bool
+JSContext::getPendingException(MutableHandleValue rval)
 {
-    RootedValue value(this, getPendingException());
+    JS_ASSERT(throwing);
+    rval.set(unwrappedException_);
+    if (IsAtomsCompartment(compartment()))
+        return true;
     clearPendingException();
-    if (!IsAtomsCompartment(compartment()) && compartment()->wrap(this, &value))
-        setPendingException(value);
+    if (!compartment()->wrap(this, rval))
+        return false;
+    assertSameCompartment(this, rval);
+    setPendingException(rval);
+    return true;
 }
 
-
 void
 JSContext::enterGenerator(JSGenerator *gen)
 {
     JS_ASSERT(!gen->prevGenerator);
     gen->prevGenerator = innermostGenerator_;
     innermostGenerator_ = gen;
 }
 
@@ -1166,19 +1166,16 @@ void
 JSContext::restoreFrameChain()
 {
     SavedFrameChain sfc = savedFrameChains_.popCopy();
     setCompartment(sfc.compartment);
     enterCompartmentDepth_ = sfc.enterCompartmentCount;
 
     if (Activation *act = mainThread().activation())
         act->restoreFrameChain();
-
-    if (isExceptionPending())
-        wrapPendingException();
 }
 
 bool
 JSContext::currentlyRunning() const
 {
     for (ActivationIterator iter(runtime()); !iter.done(); ++iter) {
         if (iter.activation()->cx() == this) {
             if (iter.activation()->hasSavedFrameChain())
@@ -1282,17 +1279,17 @@ void
 JSContext::mark(JSTracer *trc)
 {
     /* Stack frames and slots are traced by StackSpace::mark. */
 
     /* Mark other roots-by-definition in the JSContext. */
     if (defaultCompartmentObject_)
         MarkObjectRoot(trc, &defaultCompartmentObject_, "default compartment object");
     if (isExceptionPending())
-        MarkValueRoot(trc, &exception, "exception");
+        MarkValueRoot(trc, &unwrappedException_, "unwrapped exception");
 
     TraceCycleDetectionSet(trc, cycleDetectorSet);
 
     MarkValueRoot(trc, &iterValue, "iterValue");
 }
 
 JSVersion
 JSContext::findVersion() const
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -421,17 +421,17 @@ struct JSContext : public js::ExclusiveC
     JSRuntime *runtime() const { return runtime_; }
     js::PerThreadData &mainThread() const { return runtime()->mainThread; }
 
     friend class js::ExclusiveContext;
 
   private:
     /* Exception state -- the exception member is a GC root by definition. */
     bool                throwing;            /* is there a pending exception? */
-    js::Value           exception;           /* most-recently-thrown exception */
+    js::Value           unwrappedException_; /* most-recently-thrown exception */
 
     /* Per-context options. */
     JS::ContextOptions  options_;
 
   public:
     int32_t             reportGranularity;  /* see vm/Probes.h */
 
     js::AutoResolving   *resolvingList;
@@ -463,19 +463,16 @@ struct JSContext : public js::ExclusiveC
   public:
     inline void setDefaultCompartmentObject(JSObject *obj);
     inline void setDefaultCompartmentObjectIfUnset(JSObject *obj);
     JSObject *maybeDefaultCompartmentObject() const {
         JS_ASSERT(!options().noDefaultCompartmentObject());
         return defaultCompartmentObject_;
     }
 
-    /* Wrap cx->exception for the current compartment. */
-    void wrapPendingException();
-
     /* State for object and array toSource conversion. */
     js::ObjectSet       cycleDetectorSet;
 
     /* Per-context optional error reporter. */
     JSErrorReporter     errorReporter;
 
     /* Client opaque pointers. */
     void                *data;
@@ -578,26 +575,23 @@ struct JSContext : public js::ExclusiveC
     void reportAllocationOverflow() {
         js_ReportAllocationOverflow(this);
     }
 
     bool isExceptionPending() {
         return throwing;
     }
 
-    js::Value getPendingException() {
-        JS_ASSERT(throwing);
-        return exception;
-    }
+    bool getPendingException(JS::MutableHandleValue rval);
 
     void setPendingException(js::Value v);
 
     void clearPendingException() {
         throwing = false;
-        exception.setUndefined();
+        unwrappedException_.setUndefined();
     }
 
 #ifdef DEBUG
     /*
      * Controls whether a quadratic-complexity assertion is performed during
      * stack iteration; defaults to true.
      */
     bool stackIterAssertionEnabled;
--- a/js/src/jscntxtinlines.h
+++ b/js/src/jscntxtinlines.h
@@ -353,20 +353,21 @@ inline LifoAlloc &
 ExclusiveContext::typeLifoAlloc()
 {
     return zone()->types.typeLifoAlloc;
 }
 
 }  /* namespace js */
 
 inline void
-JSContext::setPendingException(js::Value v) {
+JSContext::setPendingException(js::Value v)
+{
     JS_ASSERT(!IsPoisonedValue(v));
     this->throwing = true;
-    this->exception = v;
+    this->unwrappedException_ = v;
     js::assertSameCompartment(this, v);
 }
 
 inline void
 JSContext::setDefaultCompartmentObject(JSObject *obj)
 {
     JS_ASSERT(!options().noDefaultCompartmentObject());
     defaultCompartmentObject_ = obj;
@@ -383,39 +384,29 @@ JSContext::setDefaultCompartmentObjectIf
 }
 
 inline void
 js::ExclusiveContext::enterCompartment(JSCompartment *c)
 {
     enterCompartmentDepth_++;
     c->enter();
     setCompartment(c);
-
-    if (JSContext *cx = maybeJSContext()) {
-        if (cx->throwing)
-            cx->wrapPendingException();
-    }
 }
 
 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);
     startingCompartment->leave();
-
-    if (JSContext *cx = maybeJSContext()) {
-        if (cx->throwing && oldCompartment)
-            cx->wrapPendingException();
-    }
 }
 
 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);
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -725,17 +725,20 @@ IsDuckTypedErrorObject(JSContext *cx, Ha
 }
 
 bool
 js_ReportUncaughtException(JSContext *cx)
 {
     if (!cx->isExceptionPending())
         return true;
 
-    RootedValue exn(cx, cx->getPendingException());
+    RootedValue exn(cx);
+    if (!cx->getPendingException(&exn))
+        return false;
+
     AutoValueVector roots(cx);
     roots.resize(6);
 
     /*
      * Because ToString below could error and an exception object could become
      * unrooted, we must root exnObject.  Later, if exnObject is non-null, we
      * need to root other intermediates, so allocate an operand stack segment
      * to protect all of these values.
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -973,17 +973,18 @@ js::CloseIterator(JSContext *cx, HandleO
         return CloseLegacyGenerator(cx, obj);
     }
     return true;
 }
 
 bool
 js::UnwindIteratorForException(JSContext *cx, HandleObject obj)
 {
-    RootedValue v(cx, cx->getPendingException());
+    RootedValue v(cx);
+    cx->getPendingException(&v);
     cx->clearPendingException();
     if (!CloseIterator(cx, obj))
         return false;
     cx->setPendingException(v);
     return true;
 }
 
 void
@@ -1190,17 +1191,22 @@ js_IteratorMore(JSContext *cx, HandleObj
         if ((ni->flags & JSITER_KEYVALUE) && !NewKeyValuePair(cx, id, rval, rval))
             return false;
     } else {
         /* Call the iterator object's .next method. */
         if (!JSObject::getProperty(cx, iterobj, iterobj, cx->names().next, rval))
             return false;
         if (!Invoke(cx, ObjectValue(*iterobj), rval, 0, nullptr, rval)) {
             /* Check for StopIteration. */
-            if (!cx->isExceptionPending() || !JS_IsStopIteration(cx->getPendingException()))
+            if (!cx->isExceptionPending())
+                return false;
+            RootedValue exception(cx);
+            if (!cx->getPendingException(&exception))
+                return false;
+            if (!JS_IsStopIteration(exception))
                 return false;
 
             cx->clearPendingException();
             cx->iterValue.setMagic(JS_NO_ITER_VALUE);
             rval.setBoolean(false);
             return true;
         }
     }
--- a/js/src/jswrapper.cpp
+++ b/js/src/jswrapper.cpp
@@ -144,18 +144,18 @@ js::TransparentObjectWrapper(JSContext *
     JS_ASSERT(wrappedProto == TaggedProto::LazyProto);
     return Wrapper::New(cx, obj, parent, &CrossCompartmentWrapper::singleton);
 }
 
 ErrorCopier::~ErrorCopier()
 {
     JSContext *cx = ac.ref().context()->asJSContext();
     if (ac.ref().origin() != cx->compartment() && cx->isExceptionPending()) {
-        RootedValue exc(cx, cx->getPendingException());
-        if (exc.isObject() && exc.toObject().is<ErrorObject>()) {
+        RootedValue exc(cx);
+        if (cx->getPendingException(&exc) && exc.isObject() && exc.toObject().is<ErrorObject>()) {
             cx->clearPendingException();
             ac.destroy();
             Rooted<ErrorObject*> errObj(cx, &exc.toObject().as<ErrorObject>());
             JSObject *copyobj = js_CopyErrorObject(cx, errObj, scope);
             if (copyobj)
                 cx->setPendingException(ObjectValue(*copyobj));
         }
     }
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -778,21 +778,23 @@ Debugger::unwrapDebuggeeValue(JSContext 
 
 JSTrapStatus
 Debugger::handleUncaughtExceptionHelper(Maybe<AutoCompartment> &ac,
                                         MutableHandleValue *vp, bool callHook)
 {
     JSContext *cx = ac.ref().context()->asJSContext();
     if (cx->isExceptionPending()) {
         if (callHook && uncaughtExceptionHook) {
-            Value fval = ObjectValue(*uncaughtExceptionHook);
-            Value exc = cx->getPendingException();
+            RootedValue exc(cx);
+            if (!cx->getPendingException(&exc))
+                return JSTRAP_ERROR;
+            cx->clearPendingException();
+            RootedValue fval(cx, ObjectValue(*uncaughtExceptionHook));
             RootedValue rv(cx);
-            cx->clearPendingException();
-            if (Invoke(cx, ObjectValue(*object), fval, 1, &exc, &rv))
+            if (Invoke(cx, ObjectValue(*object), fval, 1, exc.address(), &rv))
                 return vp ? parseResumptionValue(ac, true, rv, *vp, false) : JSTRAP_CONTINUE;
         }
 
         if (cx->isExceptionPending()) {
             JS_ReportPendingException(cx);
             cx->clearPendingException();
         }
     }
@@ -818,17 +820,18 @@ Debugger::resultToCompletion(JSContext *
 {
     JS_ASSERT_IF(ok, !cx->isExceptionPending());
 
     if (ok) {
         *status = JSTRAP_RETURN;
         value.set(rv);
     } else if (cx->isExceptionPending()) {
         *status = JSTRAP_THROW;
-        value.set(cx->getPendingException());
+        if (!cx->getPendingException(value))
+            *status = JSTRAP_ERROR;
         cx->clearPendingException();
     } else {
         *status = JSTRAP_ERROR;
         value.setUndefined();
     }
 }
 
 bool
@@ -979,17 +982,19 @@ Debugger::fireDebuggerStatement(JSContex
 
 JSTrapStatus
 Debugger::fireExceptionUnwind(JSContext *cx, MutableHandleValue vp)
 {
     RootedObject hook(cx, getHook(OnExceptionUnwind));
     JS_ASSERT(hook);
     JS_ASSERT(hook->isCallable());
 
-    RootedValue exc(cx, cx->getPendingException());
+    RootedValue exc(cx);
+    if (!cx->getPendingException(&exc))
+        return JSTRAP_ERROR;
     cx->clearPendingException();
 
     Maybe<AutoCompartment> ac;
     ac.construct(cx, object);
 
     Value argvData[] = { JSVAL_VOID, exc };
     AutoValueArray argv(cx, argvData, 2);
     ScriptFrameIter iter(cx);
@@ -1226,17 +1231,18 @@ Debugger::onSingleStep(JSContext *cx, Mu
      * We may be stepping over a JSOP_EXCEPTION, that pushes the context's
      * pending exception for a 'catch' clause to handle. Don't let the
      * onStep handlers mess with that (other than by returning a resumption
      * value).
      */
     RootedValue exception(cx, UndefinedValue());
     bool exceptionPending = cx->isExceptionPending();
     if (exceptionPending) {
-        exception = cx->getPendingException();
+        if (!cx->getPendingException(&exception))
+            return JSTRAP_ERROR;
         cx->clearPendingException();
     }
 
     /*
      * Build list of Debugger.Frame instances referring to this frame with
      * onStep handlers.
      */
     AutoObjectVector frames(cx);
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -988,40 +988,37 @@ HandleError(JSContext *cx, FrameRegs &re
                 ForcedReturn(cx, si, regs);
                 return SuccessfulReturnContinuation;
 
               default:
                 MOZ_ASSUME_UNREACHABLE("Invalid trap status");
             }
         }
 
+        RootedValue exception(cx);
         for (TryNoteIter tni(cx, regs); !tni.done(); ++tni) {
             JSTryNote *tn = *tni;
 
             UnwindScope(cx, si, tn->stackDepth);
 
             /*
              * Set pc to the first bytecode after the the try note to point
              * to the beginning of catch or finally or to [enditer] closing
              * the for-in loop.
              */
             regs.pc = regs.fp()->script()->main() + tn->start + tn->length;
             regs.sp = regs.spForStackDepth(tn->stackDepth);
 
             switch (tn->kind) {
               case JSTRY_CATCH:
                 /* Catch cannot intercept the closing of a generator. */
-                if (JS_UNLIKELY(cx->getPendingException().isMagic(JS_GENERATOR_CLOSING)))
+                if (!cx->getPendingException(&exception))
+                    return ErrorReturnContinuation;
+                if (exception.isMagic(JS_GENERATOR_CLOSING))
                     break;
-
-                /*
-                 * Don't clear exceptions to save cx->exception from GC
-                 * until it is pushed to the stack via [exception] in the
-                 * catch block.
-                 */
                 return CatchContinuation;
 
               case JSTRY_FINALLY:
                 return FinallyContinuation;
 
               case JSTRY_ITER: {
                 /* This is similar to JSOP_ENDITER in the interpreter loop. */
                 JS_ASSERT(JSOp(*regs.pc) == JSOP_ENDITER);
@@ -1037,21 +1034,26 @@ HandleError(JSContext *cx, FrameRegs &re
                 break;
             }
         }
 
         /*
          * Propagate the exception or error to the caller unless the exception
          * is an asynchronous return from a generator.
          */
-        if (JS_UNLIKELY(cx->isExceptionPending() &&
-                        cx->getPendingException().isMagic(JS_GENERATOR_CLOSING))) {
-            cx->clearPendingException();
-            ok = true;
-            regs.fp()->clearReturnValue();
+        if (cx->isExceptionPending()) {
+            RootedValue exception(cx);
+            if (!cx->getPendingException(&exception))
+                return ErrorReturnContinuation;
+
+            if (exception.isMagic(JS_GENERATOR_CLOSING)) {
+                cx->clearPendingException();
+                ok = true;
+                regs.fp()->clearReturnValue();
+            }
         }
     } else {
         UnwindForUncatchableException(cx, regs);
     }
 
     ForcedReturn(cx, si, regs);
     return ok ? SuccessfulReturnContinuation : ErrorReturnContinuation;
 }
@@ -3428,18 +3430,23 @@ DEFAULT()
       case CatchContinuation:
         ADVANCE_AND_DISPATCH(0);
 
       case FinallyContinuation:
         /*
          * Push (true, exception) pair for finally to indicate that [retsub]
          * should rethrow the exception.
          */
+        RootedValue &exception = rootValue0;
+        if (!cx->getPendingException(&exception)) {
+            interpReturnOK = false;
+            goto return_continuation;
+        }
         PUSH_BOOLEAN(true);
-        PUSH_COPY(cx->getPendingException());
+        PUSH_COPY(exception);
         cx->clearPendingException();
         ADVANCE_AND_DISPATCH(0);
     }
     MOZ_ASSUME_UNREACHABLE("Invalid HandleError continuation");
 
   exit:
     if (JS_UNLIKELY(cx->compartment()->debugMode()))
         interpReturnOK = ScriptDebugEpilogue(cx, REGS.fp(), REGS.pc, interpReturnOK);
@@ -3678,19 +3685,19 @@ js::SetCallOperation(JSContext *cx)
 bool
 js::GetAndClearException(JSContext *cx, MutableHandleValue res)
 {
     // Check the interrupt flag to allow interrupting deeply nested exception
     // handling.
     if (cx->runtime()->interrupt && !js_HandleExecutionInterrupt(cx))
         return false;
 
-    res.set(cx->getPendingException());
+    bool status = cx->getPendingException(res);
     cx->clearPendingException();
-    return true;
+    return status;
 }
 
 template <bool strict>
 bool
 js::SetProperty(JSContext *cx, HandleObject obj, HandleId id, const Value &value)
 {
     RootedValue v(cx, value);
     return JSObject::setGeneric(cx, obj, obj, id, &v, strict);
--- a/js/src/vm/OldDebugAPI.cpp
+++ b/js/src/vm/OldDebugAPI.cpp
@@ -627,29 +627,33 @@ GetPropertyDesc(JSContext *cx, JSObject 
 {
     assertSameCompartment(cx, obj_);
     pd->id = IdToJsval(shape->propid());
 
     RootedObject obj(cx, obj_);
 
     bool wasThrowing = cx->isExceptionPending();
     RootedValue lastException(cx, UndefinedValue());
-    if (wasThrowing)
-        lastException = cx->getPendingException();
+    if (wasThrowing) {
+        if (!cx->getPendingException(&lastException))
+            return false;
+    }
     cx->clearPendingException();
 
     Rooted<jsid> id(cx, shape->propid());
     RootedValue value(cx);
     if (!baseops::GetProperty(cx, obj, id, &value)) {
         if (!cx->isExceptionPending()) {
             pd->flags = JSPD_ERROR;
             pd->value = JSVAL_VOID;
         } else {
             pd->flags = JSPD_EXCEPTION;
-            pd->value = cx->getPendingException();
+            if (!cx->getPendingException(&value))
+                return false;
+            pd->value = value;
         }
     } else {
         pd->flags = 0;
         pd->value = value;
     }
 
     if (wasThrowing)
         cx->setPendingException(lastException);