Compartment mismatch with pending exception (bug 621845, r=lw,jorendorff).
☠☠ backed out by bbb64eb86aa2 ☠ ☠
authorAndreas Gal <gal@mozilla.com>
Thu, 06 Jan 2011 15:15:00 -0800
changeset 60178 d58e45442c87bdae66439ebb7a726dd2dd5ea70c
parent 60177 2215b740bca4c03f8ec8e90070ef48693982c306
child 60179 39ccae4efa0fbc37010acae0ac0754ed21bd8f91
child 60194 bbb64eb86aa25a27acf9638aeda1648049acc0c8
push idunknown
push userunknown
push dateunknown
reviewerslw, jorendorff
bugs621845
milestone2.0b9pre
Compartment mismatch with pending exception (bug 621845, r=lw,jorendorff).
dom/base/nsJSEnvironment.cpp
js/src/jsapi.cpp
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/jscntxtinlines.h
js/src/jscompartment.cpp
js/src/jscompartment.h
js/src/jsdbgapi.cpp
js/src/jsgc.cpp
js/src/jsinterp.cpp
js/src/jsiter.cpp
js/src/jsstr.cpp
js/src/jstracer.cpp
js/src/jswrapper.cpp
js/src/methodjit/Compiler.cpp
js/src/methodjit/InvokeHelpers.cpp
js/src/methodjit/StubCalls.cpp
js/src/methodjit/StubCalls.h
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -1125,17 +1125,17 @@ nsJSContext::DOMOperationCallback(JSCont
     switch(cx->debugHooks->debuggerHandler(cx, script, ::JS_GetFramePC(cx, fp),
                                            &rval,
                                            cx->debugHooks->
                                            debuggerHandlerData)) {
       case JSTRAP_RETURN:
         JS_SetFrameReturnValue(cx, fp, rval);
         return JS_TRUE;
       case JSTRAP_ERROR:
-        cx->throwing = JS_FALSE;
+        JS_ClearPendingException(cx);
         return JS_FALSE;
       case JSTRAP_THROW:
         JS_SetPendingException(cx, rval);
         return JS_FALSE;
       case JSTRAP_CONTINUE:
       default:
         return JS_TRUE;
     }
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -5168,17 +5168,18 @@ JS_IsRunning(JSContext *cx)
     /*
      * The use of cx->fp below is safe. Rationale: Here we don't care if the
      * interpreter state is stale. We just want to know if there *is* any
      * interpreter state.
      */
     VOUCH_DOES_NOT_REQUIRE_STACK();
 
 #ifdef JS_TRACER
-    JS_ASSERT_IF(JS_TRACE_MONITOR(cx).tracecx == cx, cx->hasfp());
+    JS_ASSERT_IF(cx->compartment &&
+                 JS_TRACE_MONITOR(cx).tracecx == cx, cx->hasfp());
 #endif
     JSStackFrame *fp = cx->maybefp();
     while (fp && fp->isDummyFrame())
         fp = fp->prev();
     return fp != NULL;
 }
 
 JS_PUBLIC_API(JSStackFrame *)
@@ -5841,42 +5842,42 @@ JS_GetLocaleCallbacks(JSContext *cx)
     return cx->localeCallbacks;
 }
 
 /************************************************************************/
 
 JS_PUBLIC_API(JSBool)
 JS_IsExceptionPending(JSContext *cx)
 {
-    return (JSBool) cx->throwing;
+    return (JSBool) cx->isExceptionPending();
 }
 
 JS_PUBLIC_API(JSBool)
 JS_GetPendingException(JSContext *cx, jsval *vp)
 {
     CHECK_REQUEST(cx);
-    if (!cx->throwing)
+    if (!cx->isExceptionPending())
         return JS_FALSE;
-    Valueify(*vp) = cx->exception;
+    Valueify(*vp) = cx->getPendingException();
+    assertSameCompartment(cx, *vp);
     return JS_TRUE;
 }
 
 JS_PUBLIC_API(void)
 JS_SetPendingException(JSContext *cx, jsval v)
 {
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, v);
-    SetPendingException(cx, Valueify(v));
+    cx->setPendingException(Valueify(v));
 }
 
 JS_PUBLIC_API(void)
 JS_ClearPendingException(JSContext *cx)
 {
-    cx->throwing = JS_FALSE;
-    cx->exception.setUndefined();
+    cx->clearPendingException();
 }
 
 JS_PUBLIC_API(JSBool)
 JS_ReportPendingException(JSContext *cx)
 {
     JSBool ok;
     JSPackedBool save;
 
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -1352,17 +1352,17 @@ js_ReportOutOfMemory(JSContext *cx)
     PopulateReportBlame(cx, &report);
 
     /*
      * If debugErrorHook is present then we give it a chance to veto sending
      * the error on to the regular ErrorReporter. We also clear a pending
      * exception if any now so the hooks can replace the out-of-memory error
      * by a script-catchable exception.
      */
-    cx->throwing = JS_FALSE;
+    cx->clearPendingException();
     if (onError) {
         JSDebugErrorHook hook = cx->debugHooks->debugErrorHook;
         if (hook &&
             !hook(cx, msg, &report, cx->debugHooks->debugErrorHookData)) {
             onError = NULL;
         }
     }
 
@@ -2000,37 +2000,47 @@ JSContext::JSContext(JSRuntime *rt)
 void
 JSContext::resetCompartment()
 {
     JSObject *scopeobj;
     if (hasfp()) {
         scopeobj = &fp()->scopeChain();
     } else {
         scopeobj = globalObject;
-        if (!scopeobj) {
-            compartment = runtime->defaultCompartment;
-            return;
-        }
+        if (!scopeobj)
+            goto error;
 
         /*
          * Innerize. Assert, but check anyway, that this succeeds. (It
          * can only fail due to bugs in the engine or embedding.)
          */
         OBJ_TO_INNER_OBJECT(this, scopeobj);
-        if (!scopeobj) {
-            /*
-             * Bug. Return NULL, not defaultCompartment, to crash rather
-             * than open a security hole.
-             */
-            JS_ASSERT(0);
-            compartment = NULL;
-            return;
-        }
+        if (!scopeobj)
+            goto error;
     }
-    compartment = scopeobj->getCompartment();
+
+    compartment = scopeobj->compartment();
+
+    /*
+     * If wrapException fails, it overrides this->exception and
+     * reports OOM. The upshot is that we silently turn the exception
+     * into an uncatchable OOM error. A bit surprising, but the
+     * caller is just going to return false either way.
+     */
+    if (isExceptionPending())
+        (void) compartment->wrapException(this);
+    return;
+
+error:
+
+    /*
+     * If we try to use the context without a selected compartment,
+     * we will crash.
+     */
+    compartment = NULL;
 }
 
 void
 JSContext::pushSegmentAndFrame(js::StackSegment *newseg, JSFrameRegs &newregs)
 {
     JS_ASSERT(regs != &newregs);
     if (hasActiveSegment())
         currentSegment->suspend(regs);
@@ -2284,16 +2294,9 @@ JS_FORCES_STACK JS_FRIEND_API(void)
 LeaveTrace(JSContext *cx)
 {
 #ifdef JS_TRACER
     if (JS_ON_TRACE(cx))
         DeepBail(cx);
 #endif
 }
 
-void
-SetPendingException(JSContext *cx, const Value &v)
-{
-    cx->throwing = JS_TRUE;
-    cx->exception = v;
-}
-
 } /* namespace js */
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -883,17 +883,17 @@ private:
 } /* namespace js */
 
 /*
  * N.B. JS_ON_TRACE(cx) is true if JIT code is on the stack in the current
  * thread, regardless of whether cx is the context in which that trace is
  * executing.  cx must be a context on the current thread.
  */
 #ifdef JS_TRACER
-# define JS_ON_TRACE(cx)            (JS_TRACE_MONITOR(cx).ontrace())
+# define JS_ON_TRACE(cx)            (cx->compartment && JS_TRACE_MONITOR(cx).ontrace())
 #else
 # define JS_ON_TRACE(cx)            false
 #endif
 
 #ifdef DEBUG
 # define FUNCTION_KIND_METER_LIST(_)                                          \
                         _(allfun), _(heavy), _(nofreeupvar), _(onlyfreevar),  \
                         _(display), _(flat), _(setupvar), _(badfunarg),       \
@@ -1695,16 +1695,20 @@ struct JSContext
     JSCList             link;
 
   private:
     /* See JSContext::findVersion. */
     JSVersion           defaultVersion;      /* script compilation version */
     JSVersion           versionOverride;     /* supercedes defaultVersion when valid */
     bool                hasVersionOverride;
 
+    /* Exception state -- the exception member is a GC root by definition. */
+    JSBool              throwing;           /* is there a pending exception? */
+    js::Value           exception;          /* most-recently-thrown exception */
+
   public:
     /* Per-context options. */
     uint32              options;            /* see jsapi.h for JSOPTION_* */
 
     /* Locale specific callbacks for string conversion. */
     JSLocaleCallbacks   *localeCallbacks;
 
     /*
@@ -1716,20 +1720,16 @@ struct JSContext
     JSDHashTable        *resolvingTable;
 
     /*
      * True if generating an error, to prevent runaway recursion.
      * NB: generatingError packs with throwing below.
      */
     JSPackedBool        generatingError;
 
-    /* Exception state -- the exception member is a GC root by definition. */
-    JSBool              throwing;           /* is there a pending exception? */
-    js::Value           exception;          /* most-recently-thrown exception */
-
     /* Limit pointer for checking native stack consumption during recursion. */
     jsuword             stackLimit;
 
     /* Quota on the size of arenas used to compile and execute scripts. */
     size_t              scriptStackQuota;
 
     /* Data shared by threads in an address space. */
     JSRuntime *const    runtime;
@@ -2163,16 +2163,32 @@ struct JSContext
     void assertValidStackDepth(uintN depth) {
         JS_ASSERT(0 <= regs->sp - regs->fp->base());
         JS_ASSERT(depth <= uintptr_t(regs->sp - regs->fp->base()));
     }
 #else
     void assertValidStackDepth(uintN /*depth*/) {}
 #endif
 
+    bool isExceptionPending() {
+        return throwing;
+    }
+
+    js::Value getPendingException() {
+        JS_ASSERT(throwing);
+        return exception;
+    }
+
+    void setPendingException(js::Value v);
+
+    void clearPendingException() {
+        this->throwing = false;
+        this->exception.setUndefined();
+    }
+
   private:
     /*
      * The allocation code calls the function to indicate either OOM failure
      * when p is null or that a memory pressure counter has reached some
      * threshold when p is not null. The function takes the pointer and not
      * a boolean flag to minimize the amount of code in its inlined callers.
      */
     JS_FRIEND_API(void) checkMallocGCPressure(void *p);
@@ -3142,19 +3158,16 @@ js_GetScriptedCaller(JSContext *cx, JSSt
 extern jsbytecode*
 js_GetCurrentBytecodePC(JSContext* cx);
 
 extern bool
 js_CurrentPCIsInImacro(JSContext *cx);
 
 namespace js {
 
-extern void
-SetPendingException(JSContext *cx, const Value &v);
-
 class RegExpStatics;
 
 extern JS_FORCES_STACK JS_FRIEND_API(void)
 LeaveTrace(JSContext *cx);
 
 } /* namespace js */
 
 /*
--- a/js/src/jscntxtinlines.h
+++ b/js/src/jscntxtinlines.h
@@ -681,23 +681,23 @@ assertSameCompartment(JSContext *cx, T1 
 
 #undef START_ASSERT_SAME_COMPARTMENT
 
 STATIC_PRECONDITION_ASSUME(ubound(vp) >= argc + 2)
 JS_ALWAYS_INLINE bool
 CallJSNative(JSContext *cx, js::Native native, uintN argc, js::Value *vp)
 {
 #ifdef DEBUG
-    JSBool alreadyThrowing = cx->throwing;
+    JSBool alreadyThrowing = cx->isExceptionPending();
 #endif
     assertSameCompartment(cx, ValueArray(vp, argc + 2));
     JSBool ok = native(cx, argc, vp);
     if (ok) {
         assertSameCompartment(cx, vp[0]);
-        JS_ASSERT_IF(!alreadyThrowing, !cx->throwing);
+        JS_ASSERT_IF(!alreadyThrowing, !cx->isExceptionPending());
     }
     return ok;
 }
 
 STATIC_PRECONDITION(ubound(vp) >= argc + 2)
 JS_ALWAYS_INLINE bool
 CallJSNativeConstructor(JSContext *cx, js::Native native, uintN argc, js::Value *vp)
 {
@@ -795,9 +795,16 @@ CanLeaveTrace(JSContext *cx)
     return cx->bailExit != NULL;
 #else
     return JS_FALSE;
 #endif
 }
 
 }  /* namespace js */
 
+inline void
+JSContext::setPendingException(js::Value v) {
+    this->throwing = true;
+    this->exception = v;
+    assertSameCompartment(this, v);
+}
+
 #endif /* jscntxtinlines_h___ */
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -353,24 +353,21 @@ JSCompartment::wrap(JSContext *cx, AutoI
     return true;
 }
 
 bool
 JSCompartment::wrapException(JSContext *cx)
 {
     JS_ASSERT(cx->compartment == this);
 
-    if (cx->throwing) {
-        AutoValueRooter tvr(cx, cx->exception);
-        cx->throwing = false;
-        cx->exception.setNull();
-        if (wrap(cx, tvr.addr())) {
-            cx->throwing = true;
-            cx->exception = tvr.value();
-        }
+    if (cx->isExceptionPending()) {
+        Value v = cx->getPendingException();
+        cx->clearPendingException();
+        if (wrap(cx, &v))
+            cx->setPendingException(v);
         return false;
     }
     return true;
 }
 
 #if defined JS_METHODJIT && defined JS_MONOIC
 /*
  * Check if the pool containing the code for jit should be destroyed, per the
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -372,16 +372,17 @@ class PreserveCompartment {
   protected:
     JSContext *cx;
   private:
     JSCompartment *oldCompartment;
     JS_DECL_USE_GUARD_OBJECT_NOTIFIER
   public:
      PreserveCompartment(JSContext *cx JS_GUARD_OBJECT_NOTIFIER_PARAM) : cx(cx) {
         JS_GUARD_OBJECT_NOTIFIER_INIT;
+        JS_ASSERT(!cx->isExceptionPending());
         oldCompartment = cx->compartment;
     }
 
     ~PreserveCompartment() {
         cx->compartment = oldCompartment;
     }
 };
 
--- a/js/src/jsdbgapi.cpp
+++ b/js/src/jsdbgapi.cpp
@@ -1540,35 +1540,36 @@ JS_PropertyIterator(JSObject *obj, JSSco
 JS_PUBLIC_API(JSBool)
 JS_GetPropertyDesc(JSContext *cx, JSObject *obj, JSScopeProperty *sprop,
                    JSPropertyDesc *pd)
 {
     assertSameCompartment(cx, obj);
     Shape *shape = (Shape *) sprop;
     pd->id = IdToJsval(shape->id);
 
-    JSBool wasThrowing = cx->throwing;
-    AutoValueRooter lastException(cx, cx->exception);
-    cx->throwing = JS_FALSE;
+    JSBool wasThrowing = cx->isExceptionPending();
+    Value lastException = UndefinedValue();
+    if (wasThrowing)
+        lastException = cx->getPendingException();
+    cx->clearPendingException();
 
     if (!js_GetProperty(cx, obj, shape->id, Valueify(&pd->value))) {
-        if (!cx->throwing) {
+        if (!cx->isExceptionPending()) {
             pd->flags = JSPD_ERROR;
             pd->value = JSVAL_VOID;
         } else {
             pd->flags = JSPD_EXCEPTION;
-            pd->value = Jsvalify(cx->exception);
+            pd->value = Jsvalify(cx->getPendingException());
         }
     } else {
         pd->flags = 0;
     }
 
-    cx->throwing = wasThrowing;
     if (wasThrowing)
-        cx->exception = lastException.value();
+        cx->setPendingException(lastException);
 
     pd->flags |= (shape->enumerable() ? JSPD_ENUMERATE : 0)
               |  (!shape->writable()  ? JSPD_READONLY  : 0)
               |  (!shape->configurable() ? JSPD_PERMANENT : 0);
     pd->spare = 0;
     if (shape->getter() == GetCallArg) {
         pd->slot = shape->shortid;
         pd->flags |= JSPD_ARGUMENT;
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -1577,22 +1577,18 @@ namespace js {
 void
 MarkContext(JSTracer *trc, JSContext *acx)
 {
     /* Stack frames and slots are traced by StackSpace::mark. */
 
     /* Mark other roots-by-definition in acx. */
     if (acx->globalObject && !JS_HAS_OPTION(acx, JSOPTION_UNROOTED_GLOBAL))
         MarkObject(trc, *acx->globalObject, "global object");
-    if (acx->throwing) {
-        MarkValue(trc, acx->exception, "exception");
-    } else {
-        /* Avoid keeping GC-ed junk stored in JSContext.exception. */
-        acx->exception.setNull();
-    }
+    if (acx->isExceptionPending())
+        MarkValue(trc, acx->getPendingException(), "exception");
 
     for (js::AutoGCRooter *gcr = acx->autoGCRooters; gcr; gcr = gcr->down)
         gcr->trace(trc);
 
     if (acx->sharpObjectMap.depth > 0)
         js_TraceSharpMap(trc, &acx->sharpObjectMap);
 
     MarkValue(trc, acx->iterValue, "iterValue");
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -2424,33 +2424,33 @@ Interpret(JSContext *cx, JSStackFrame *e
 #endif
 
 #define RESTORE_INTERP_VARS()                                                 \
     JS_BEGIN_MACRO                                                            \
         script = regs.fp->script();                                           \
         argv = regs.fp->maybeFormalArgs();                                    \
         atoms = FrameAtomBase(cx, regs.fp);                                   \
         JS_ASSERT(cx->regs == &regs);                                         \
-        if (cx->throwing)                                                     \
+        if (cx->isExceptionPending())                                         \
             goto error;                                                       \
     JS_END_MACRO
 
 #define MONITOR_BRANCH()                                                      \
     JS_BEGIN_MACRO                                                            \
         if (TRACING_ENABLED(cx)) {                                            \
             MonitorResult r = MonitorLoopEdge(cx, inlineCallCount);           \
             if (r == MONITOR_RECORDING) {                                     \
                 JS_ASSERT(TRACE_RECORDER(cx));                                \
                 JS_ASSERT(!TRACE_PROFILER(cx));                               \
                 MONITOR_BRANCH_TRACEVIS;                                      \
                 ENABLE_INTERRUPTS();                                          \
                 CLEAR_LEAVE_ON_TRACE_POINT();                                 \
             }                                                                 \
             RESTORE_INTERP_VARS();                                            \
-            JS_ASSERT_IF(cx->throwing, r == MONITOR_ERROR);                   \
+            JS_ASSERT_IF(cx->isExceptionPending(), r == MONITOR_ERROR);       \
             if (r == MONITOR_ERROR)                                           \
                 goto error;                                                   \
         }                                                                     \
     JS_END_MACRO
 
 #else /* !JS_TRACER */
 
 #define MONITOR_BRANCH() ((void) 0)
@@ -2566,19 +2566,19 @@ Interpret(JSContext *cx, JSStackFrame *e
 #if JS_HAS_GENERATORS
     if (JS_UNLIKELY(regs.fp->isGeneratorFrame())) {
         JS_ASSERT(interpGuard.prevContextRegs == &cx->generatorFor(regs.fp)->regs);
         JS_ASSERT((size_t) (regs.pc - script->code) <= script->length);
         JS_ASSERT((size_t) (regs.sp - regs.fp->base()) <= StackDepth(script));
 
         /*
          * To support generator_throw and to catch ignored exceptions,
-         * fail if cx->throwing is set.
+         * fail if cx->isExceptionPending() is true.
          */
-        if (cx->throwing)
+        if (cx->isExceptionPending())
             goto error;
     }
 #endif
 
 #ifdef JS_TRACER
     /*
      * The method JIT may have already initiated a recording, in which case
      * there should already be a valid recorder. Otherwise...
@@ -2682,18 +2682,17 @@ Interpret(JSContext *cx, JSStackFrame *e
                 goto error;
               case JSTRAP_CONTINUE:
                 break;
               case JSTRAP_RETURN:
                 regs.fp->setReturnValue(rval);
                 interpReturnOK = JS_TRUE;
                 goto forced_return;
               case JSTRAP_THROW:
-                cx->throwing = JS_TRUE;
-                cx->exception = rval;
+                cx->setPendingException(rval);
                 goto error;
               default:;
             }
             moreInterrupts = true;
         }
 
 #ifdef JS_TRACER
 #ifdef JS_METHODJIT
@@ -2711,17 +2710,17 @@ Interpret(JSContext *cx, JSStackFrame *e
                     moreInterrupts = true;
                     break;
             }
         }
 #endif
         if (TraceRecorder* tr = TRACE_RECORDER(cx)) {
             JS_ASSERT(!TRACE_PROFILER(cx));
             AbortableRecordingStatus status = tr->monitorRecording(op);
-            JS_ASSERT_IF(cx->throwing, status == ARECORD_ERROR);
+            JS_ASSERT_IF(cx->isExceptionPending(), status == ARECORD_ERROR);
 
             if (interpMode != JSINTERP_NORMAL) {
                 JS_ASSERT(interpMode == JSINTERP_RECORD || JSINTERP_SAFEPOINT);
                 switch (status) {
                   case ARECORD_IMACRO_ABORTED:
                   case ARECORD_ABORTED:
                   case ARECORD_COMPLETED:
                   case ARECORD_STOP:
@@ -5199,18 +5198,17 @@ BEGIN_CASE(JSOP_TRAP)
     switch (status) {
       case JSTRAP_ERROR:
         goto error;
       case JSTRAP_RETURN:
         regs.fp->setReturnValue(rval);
         interpReturnOK = JS_TRUE;
         goto forced_return;
       case JSTRAP_THROW:
-        cx->throwing = JS_TRUE;
-        cx->exception = rval;
+        cx->setPendingException(rval);
         goto error;
       default:
         break;
     }
     JS_ASSERT(status == JSTRAP_CONTINUE);
     CHECK_INTERRUPT_HANDLER();
     JS_ASSERT(rval.isInt32());
     op = (JSOp) rval.toInt32();
@@ -6226,57 +6224,60 @@ BEGIN_CASE(JSOP_RETSUB)
     JS_ASSERT(lval.isBoolean());
     if (lval.toBoolean()) {
         /*
          * Exception was pending during finally, throw it *before* we adjust
          * pc, because pc indexes into script->trynotes.  This turns out not to
          * be necessary, but it seems clearer.  And it points out a FIXME:
          * 350509, due to Igor Bukanov.
          */
-        cx->throwing = JS_TRUE;
-        cx->exception = rval;
+        cx->setPendingException(rval);
         goto error;
     }
     JS_ASSERT(rval.isInt32());
     len = rval.toInt32();
     regs.pc = script->main;
 END_VARLEN_CASE
 }
 
 BEGIN_CASE(JSOP_EXCEPTION)
-    JS_ASSERT(cx->throwing);
-    PUSH_COPY(cx->exception);
-    cx->throwing = JS_FALSE;
+    PUSH_COPY(cx->getPendingException());
+    cx->clearPendingException();
 #if defined(JS_TRACER) && defined(JS_METHODJIT)
     if (interpMode == JSINTERP_PROFILE) {
         leaveOnSafePoint = true;
         LEAVE_ON_SAFE_POINT();
     }
 #endif
     CHECK_BRANCH();
 END_CASE(JSOP_EXCEPTION)
 
 BEGIN_CASE(JSOP_FINALLY)
     CHECK_BRANCH();
 END_CASE(JSOP_FINALLY)
 
 BEGIN_CASE(JSOP_THROWING)
-    JS_ASSERT(!cx->throwing);
-    cx->throwing = JS_TRUE;
-    POP_COPY_TO(cx->exception);
+{
+    JS_ASSERT(!cx->isExceptionPending());
+    Value v;
+    POP_COPY_TO(v);
+    cx->setPendingException(v);
+}
 END_CASE(JSOP_THROWING)
 
 BEGIN_CASE(JSOP_THROW)
-    JS_ASSERT(!cx->throwing);
+{
+    JS_ASSERT(!cx->isExceptionPending());
     CHECK_BRANCH();
-    cx->throwing = JS_TRUE;
-    POP_COPY_TO(cx->exception);
+    Value v;
+    POP_COPY_TO(v);
+    cx->setPendingException(v);
     /* let the code at error try to catch the exception. */
     goto error;
-
+}
 BEGIN_CASE(JSOP_SETLOCALPOP)
 {
     /*
      * The stack must have a block with at least one local slot below the
      * exception object.
      */
     JS_ASSERT((size_t) (regs.sp - regs.fp->base()) >= 2);
     uint32 slot = GET_UINT16(regs.pc);
@@ -6342,18 +6343,17 @@ BEGIN_CASE(JSOP_DEBUGGER)
             goto error;
         case JSTRAP_CONTINUE:
             break;
         case JSTRAP_RETURN:
             regs.fp->setReturnValue(rval);
             interpReturnOK = JS_TRUE;
             goto forced_return;
         case JSTRAP_THROW:
-            cx->throwing = JS_TRUE;
-            cx->exception = rval;
+            cx->setPendingException(rval);
             goto error;
         default:;
         }
         CHECK_INTERRUPT_HANDLER();
     }
 }
 END_CASE(JSOP_DEBUGGER)
 #endif /* JS_HAS_DEBUGGER_KEYWORD */
@@ -6715,31 +6715,31 @@ BEGIN_CASE(JSOP_LEAVEBLOCK)
         JS_ASSERT(regs.fp->base() + blockDepth == regs.sp);
     }
 }
 END_CASE(JSOP_LEAVEBLOCK)
 
 #if JS_HAS_GENERATORS
 BEGIN_CASE(JSOP_GENERATOR)
 {
-    JS_ASSERT(!cx->throwing);
+    JS_ASSERT(!cx->isExceptionPending());
     regs.pc += JSOP_GENERATOR_LENGTH;
     JSObject *obj = js_NewGenerator(cx);
     if (!obj)
         goto error;
     JS_ASSERT(!regs.fp->hasCallObj() && !regs.fp->hasArgsObj());
     regs.fp->setReturnValue(ObjectValue(*obj));
     interpReturnOK = true;
     if (entryFrame != regs.fp)
         goto inline_return;
     goto exit;
 }
 
 BEGIN_CASE(JSOP_YIELD)
-    JS_ASSERT(!cx->throwing);
+    JS_ASSERT(!cx->isExceptionPending());
     JS_ASSERT(regs.fp->isFunctionFrame() && !regs.fp->isEvalFrame());
     if (cx->generatorFor(regs.fp)->state == JSGEN_CLOSING) {
         js_ReportValueError(cx, JSMSG_BAD_GENERATOR_YIELD,
                             JSDVG_SEARCH_STACK, argv[-2], NULL);
         goto error;
     }
     regs.fp->setReturnValue(regs.sp[-1]);
     regs.fp->setYielding();
@@ -6825,17 +6825,17 @@ END_CASE(JSOP_ARRAYPUSH)
 #if !JS_THREADED_INTERP
         } /* switch (op) */
     } /* for (;;) */
 #endif /* !JS_THREADED_INTERP */
 
   error:
     JS_ASSERT(cx->regs == &regs);
 #ifdef JS_TRACER
-    if (regs.fp->hasImacropc() && cx->throwing) {
+    if (regs.fp->hasImacropc() && cx->isExceptionPending()) {
         // Handle exceptions as if they came from the imacro-calling pc.
         regs.pc = regs.fp->imacropc();
         regs.fp->clearImacropc();
     }
 #endif
 
     JS_ASSERT(size_t((regs.fp->hasImacropc() ? regs.fp->imacropc() : regs.pc) - script->code) <
               script->length);
@@ -6849,17 +6849,17 @@ END_CASE(JSOP_ARRAYPUSH)
     if (TRACE_RECORDER(cx))
         AbortRecording(cx, "error or exception while recording");
 # ifdef JS_METHODJIT
     if (TRACE_PROFILER(cx))
         AbortProfiling(cx);
 # endif
 #endif
 
-    if (!cx->throwing) {
+    if (!cx->isExceptionPending()) {
         /* This is an error, not a catchable exception, quit the frame ASAP. */
         interpReturnOK = JS_FALSE;
     } else {
         JSThrowHook handler;
         JSTryNote *tn, *tnlimit;
         uint32 offset;
 
         /* Restore atoms local in case we will resume. */
@@ -6867,25 +6867,25 @@ END_CASE(JSOP_ARRAYPUSH)
 
         /* Call debugger throw hook if set. */
         handler = cx->debugHooks->throwHook;
         if (handler) {
             Value rval;
             switch (handler(cx, script, regs.pc, Jsvalify(&rval),
                             cx->debugHooks->throwHookData)) {
               case JSTRAP_ERROR:
-                cx->throwing = JS_FALSE;
+                cx->clearPendingException();
                 goto error;
               case JSTRAP_RETURN:
-                cx->throwing = JS_FALSE;
+                cx->clearPendingException();
                 regs.fp->setReturnValue(rval);
                 interpReturnOK = JS_TRUE;
                 goto forced_return;
               case JSTRAP_THROW:
-                cx->exception = rval;
+                cx->setPendingException(rval);
               case JSTRAP_CONTINUE:
               default:;
             }
             CHECK_INTERRUPT_HANDLER();
         }
 
         /*
          * Look for a try block in script that can catch this exception.
@@ -6938,79 +6938,78 @@ END_CASE(JSOP_ARRAYPUSH)
                  */
                 goto error;
             }
 
             switch (tn->kind) {
               case JSTRY_CATCH:
 #if JS_HAS_GENERATORS
                 /* Catch cannot intercept the closing of a generator. */
-                if (JS_UNLIKELY(cx->exception.isMagic(JS_GENERATOR_CLOSING)))
+                  if (JS_UNLIKELY(cx->getPendingException().isMagic(JS_GENERATOR_CLOSING)))
                     break;
 #endif
 
                 /*
-                 * Don't clear cx->throwing to save cx->exception from GC
+                 * Don't clear exceptions to save cx->exception from GC
                  * until it is pushed to the stack via [exception] in the
                  * catch block.
                  */
                 len = 0;
                 DO_NEXT_OP(len);
 
               case JSTRY_FINALLY:
                 /*
                  * Push (true, exception) pair for finally to indicate that
                  * [retsub] should rethrow the exception.
                  */
                 PUSH_BOOLEAN(true);
-                PUSH_COPY(cx->exception);
-                cx->throwing = JS_FALSE;
+                PUSH_COPY(cx->getPendingException());
+                cx->clearPendingException();
                 len = 0;
                 DO_NEXT_OP(len);
 
               case JSTRY_ITER: {
                 /* This is similar to JSOP_ENDITER in the interpreter loop. */
                 JS_ASSERT(js_GetOpcode(cx, regs.fp->script(), regs.pc) == JSOP_ENDITER);
-                AutoValueRooter tvr(cx, cx->exception);
-                cx->throwing = false;
+                Value v = cx->getPendingException();
+                cx->clearPendingException();
                 ok = js_CloseIterator(cx, &regs.sp[-1].toObject());
                 regs.sp -= 1;
                 if (!ok)
                     goto error;
-                cx->throwing = true;
-                cx->exception = tvr.value();
+                cx->setPendingException(v);
               }
            }
         } while (++tn != tnlimit);
 
       no_catch:
         /*
          * Propagate the exception or error to the caller unless the exception
          * is an asynchronous return from a generator.
          */
         interpReturnOK = JS_FALSE;
 #if JS_HAS_GENERATORS
-        if (JS_UNLIKELY(cx->throwing &&
-                        cx->exception.isMagic(JS_GENERATOR_CLOSING))) {
-            cx->throwing = JS_FALSE;
+        if (JS_UNLIKELY(cx->isExceptionPending() &&
+                        cx->getPendingException().isMagic(JS_GENERATOR_CLOSING))) {
+            cx->clearPendingException();
             interpReturnOK = JS_TRUE;
             regs.fp->clearReturnValue();
         }
 #endif
     }
 
   forced_return:
     /*
      * Unwind the scope making sure that interpReturnOK stays false even when
      * js_UnwindScope returns true.
      *
      * When a trap handler returns JSTRAP_RETURN, we jump here with
      * interpReturnOK set to true bypassing any finally blocks.
      */
-    interpReturnOK &= js_UnwindScope(cx, 0, interpReturnOK || cx->throwing);
+    interpReturnOK &= js_UnwindScope(cx, 0, interpReturnOK || cx->isExceptionPending());
     JS_ASSERT(regs.sp == regs.fp->base());
 
 #ifdef DEBUG
     cx->logPrevPc = NULL;
 #endif
 
     if (entryFrame != regs.fp)
         goto inline_return;
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -762,17 +762,17 @@ Iterator(JSContext *cx, uintN argc, Valu
 
 JSBool
 js_ThrowStopIteration(JSContext *cx)
 {
     Value v;
 
     JS_ASSERT(!JS_IsExceptionPending(cx));
     if (js_FindClassObject(cx, NULL, JSProto_StopIteration, &v))
-        SetPendingException(cx, v);
+        cx->setPendingException(v);
     return JS_FALSE;
 }
 
 static JSBool
 iterator_next(JSContext *cx, uintN argc, Value *vp)
 {
     JSObject *obj;
 
@@ -1012,22 +1012,20 @@ js_IteratorMore(JSContext *cx, JSObject 
     }
 
     /* Fetch and cache the next value from the iterator. */
     jsid id = ATOM_TO_JSID(cx->runtime->atomState.nextAtom);
     if (!js_GetMethod(cx, iterobj, id, JSGET_METHOD_BARRIER, rval))
         return false;
     if (!ExternalInvoke(cx, iterobj, *rval, 0, NULL, rval)) {
         /* Check for StopIteration. */
-        if (!cx->throwing || !js_ValueIsStopIteration(cx->exception))
+        if (!cx->isExceptionPending() || !js_ValueIsStopIteration(cx->getPendingException()))
             return false;
 
-        /* Inline JS_ClearPendingException(cx). */
-        cx->throwing = JS_FALSE;
-        cx->exception.setUndefined();
+        cx->clearPendingException();
         cx->iterValue.setMagic(JS_NO_ITER_VALUE);
         rval->setBoolean(false);
         return true;
     }
 
     /* Cache the value returned by iterobj.next() so js_IteratorNext() can find it. */
     JS_ASSERT(!rval->isMagic(JS_NO_ITER_VALUE));
     cx->iterValue = *rval;
@@ -1281,23 +1279,23 @@ SendToGenerator(JSContext *cx, JSGenerat
              * expression.
              */
             gen->regs.sp[-1] = arg;
         }
         gen->state = JSGEN_RUNNING;
         break;
 
       case JSGENOP_THROW:
-        SetPendingException(cx, arg);
+        cx->setPendingException(arg);
         gen->state = JSGEN_RUNNING;
         break;
 
       default:
         JS_ASSERT(op == JSGENOP_CLOSE);
-        SetPendingException(cx, MagicValue(JS_GENERATOR_CLOSING));
+        cx->setPendingException(MagicValue(JS_GENERATOR_CLOSING));
         gen->state = JSGEN_CLOSING;
         break;
     }
 
     JSStackFrame *genfp = gen->floatingFrame();
     Value *genvp = gen->floatingStack;
     uintN vplen = genfp->formalArgsEnd() - genvp;
 
@@ -1345,17 +1343,17 @@ SendToGenerator(JSContext *cx, JSGenerat
         genfp->setFloatingGenerator();
     }
     MUST_FLOW_LABEL(restore)
     RebaseRegsFromTo(&gen->regs, stackfp, genfp);
 
     if (gen->floatingFrame()->isYielding()) {
         /* Yield cannot fail, throw or be called on closing. */
         JS_ASSERT(ok);
-        JS_ASSERT(!cx->throwing);
+        JS_ASSERT(!cx->isExceptionPending());
         JS_ASSERT(gen->state == JSGEN_RUNNING);
         JS_ASSERT(op != JSGENOP_CLOSE);
         genfp->clearYielding();
         gen->state = JSGEN_OPEN;
         return JS_TRUE;
     }
 
     genfp->clearReturnValue();
@@ -1431,17 +1429,17 @@ generator_op(JSContext *cx, JSGeneratorO
         }
     } else if (gen->state == JSGEN_CLOSED) {
       closed_generator:
         switch (op) {
           case JSGENOP_NEXT:
           case JSGENOP_SEND:
             return js_ThrowStopIteration(cx);
           case JSGENOP_THROW:
-            SetPendingException(cx, argc >= 1 ? vp[2] : UndefinedValue());
+            cx->setPendingException(argc >= 1 ? vp[2] : UndefinedValue());
             return JS_FALSE;
           default:
             JS_ASSERT(op == JSGENOP_CLOSE);
             return JS_TRUE;
         }
     }
 
     bool undef = ((op == JSGENOP_SEND || op == JSGENOP_THROW) && argc != 0);
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -1662,17 +1662,17 @@ class RegExpGuard
 
     /*
      * Attempt to match |patstr| to |textstr|. A flags argument, metachars in the
      * pattern string, or a lengthy pattern string can thwart this process.
      *
      * @param checkMetaChars    Look for regexp metachars in the pattern string.
      * @return                  Whether flat matching could be used.
      *
-     * N.B. tryFlatMatch returns NULL on OOM, so the caller must check cx->throwing.
+     * N.B. tryFlatMatch returns NULL on OOM, so the caller must check cx->isExceptionPending().
      */
     const FlatMatch *
     tryFlatMatch(JSContext *cx, JSString *textstr, uintN optarg, uintN argc,
                  bool checkMetaChars = true)
     {
         if (rep.hasRegExp())
             return NULL;
 
@@ -1851,17 +1851,17 @@ str_match(JSContext *cx, uintN argc, Val
     JSString *str;
     NORMALIZE_THIS(cx, vp, str);
 
     RegExpGuard g(cx);
     if (!g.init(argc, vp))
         return false;
     if (const FlatMatch *fm = g.tryFlatMatch(cx, str, 1, argc))
         return BuildFlatMatchArray(cx, str, *fm, vp);
-    if (cx->throwing)  /* from tryFlatMatch */
+    if (cx->isExceptionPending())  /* from tryFlatMatch */
         return false;
 
     const RegExpPair *rep = g.normalizeRegExp(false, 1, argc, vp);
     if (!rep)
         return false;
 
     AutoObjectRooter array(cx);
     MatchArgType arg = array.addr();
@@ -1883,17 +1883,17 @@ str_search(JSContext *cx, uintN argc, Va
 
     RegExpGuard g(cx);
     if (!g.init(argc, vp))
         return false;
     if (const FlatMatch *fm = g.tryFlatMatch(cx, str, 1, argc)) {
         vp->setInt32(fm->match());
         return true;
     }
-    if (cx->throwing)  /* from tryFlatMatch */
+    if (cx->isExceptionPending())  /* from tryFlatMatch */
         return false;
     const RegExpPair *rep = g.normalizeRegExp(false, 1, argc, vp);
     if (!rep)
         return false;
 
     RegExpStatics *res = cx->regExpStatics();
     size_t i = 0;
     if (!rep->re().execute(cx, res, str, &i, true, vp))
@@ -2496,17 +2496,17 @@ js::str_replace(JSContext *cx, uintN arg
      * However, if the user invokes our (non-standard) |flags| argument
      * extension then we revert to creating a regular expression. Note that
      * this is observable behavior through the side-effect mutation of the
      * |RegExp| statics.
      */
 
     const FlatMatch *fm = rdata.g.tryFlatMatch(cx, rdata.str, optarg, argc, false);
     if (!fm) {
-        if (cx->throwing)  /* oom in RopeMatch in tryFlatMatch */
+        if (cx->isExceptionPending())  /* oom in RopeMatch in tryFlatMatch */
             return false;
         JS_ASSERT_IF(!rdata.g.hasRegExpPair(), argc > optarg);
         return str_replace_regexp(cx, argc, vp, rdata);
     }
 
     if (fm->match() < 0) {
         vp->setString(rdata.str);
         return true;
--- a/js/src/jstracer.cpp
+++ b/js/src/jstracer.cpp
@@ -6563,17 +6563,17 @@ ExecuteTree(JSContext* cx, TreeFragment*
     JS_ASSERT(*(uint64*)&tm->storage->global()[globalSlots] == 0xdeadbeefdeadbeefLL);
     JS_ASSERT_IF(lr->exitType == LOOP_EXIT, !lr->calldepth);
 
     /* Restore interpreter state. */
     LeaveTree(tm, state, lr);
 
     *lrp = state.innermost;
     bool ok = !(state.builtinStatus & BUILTIN_ERROR);
-    JS_ASSERT_IF(cx->throwing, !ok);
+    JS_ASSERT_IF(cx->isExceptionPending(), !ok);
 
     size_t iters = tm->iterationCounter;
 
     f->execs++;
     f->iters += iters;
 
 #ifdef DEBUG
     JSStackFrame *fp = cx->fp();
@@ -16587,17 +16587,17 @@ RecordTracePoint(JSContext* cx, uintN& i
 
   interpret:
     JS_ASSERT(TRACE_RECORDER(cx));
 
     /* Locked and loaded with a recorder. Ask the interperter to go run some code. */
     if (!Interpret(cx, fp, inlineCallCount, JSINTERP_RECORD))
         return TPA_Error;
 
-    JS_ASSERT(!cx->throwing);
+    JS_ASSERT(!cx->isExceptionPending());
     
     return TPA_RanStuff;
 }
 
 LoopProfile::LoopProfile(JSStackFrame *entryfp, jsbytecode *top, jsbytecode *bottom)
     : entryScript(entryfp->script()),
       entryfp(entryfp),
       top(top),
@@ -16754,17 +16754,17 @@ MonitorTracePoint(JSContext *cx, uintN& 
     debug_only_printf(LC_TMProfiler, "Profiling at line %d\n",
                       js_FramePCToLineNumber(cx, cx->fp()));
 
     tm->profile = prof;
 
     if (!Interpret(cx, cx->fp(), inlineCallCount, JSINTERP_PROFILE))
         return TPA_Error;
 
-    JS_ASSERT(!cx->throwing);
+    JS_ASSERT(!cx->isExceptionPending());
     
     return TPA_RanStuff;
 }
 
 /*
  * Returns true if pc is within the given loop.
  * If we're in a different script, then we must have come from
  * a call instruction within the loop (since we check if we're within
--- a/js/src/jswrapper.cpp
+++ b/js/src/jswrapper.cpp
@@ -353,17 +353,18 @@ AutoCompartment::enter()
         context->resetCompartment();
         wasSane = (context->compartment == oldCompartment);
 #endif
 
         context->compartment = destination;
         JSObject *scopeChain = target->getGlobal();
         JS_ASSERT(scopeChain->isNative());
         frame.construct();
-        if (!context->stack().pushDummyFrame(context, *scopeChain, &frame.ref())) {
+        if (!context->stack().pushDummyFrame(context, *scopeChain, &frame.ref()) ||
+            !destination->wrapException(context)) {
             frame.destroy();
             context->compartment = origin;
             return false;
         }
     }
     entered = true;
     return true;
 }
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -1715,26 +1715,19 @@ mjit::Compiler::generateMethod()
           END_CASE(JSOP_IN)
 
           BEGIN_CASE(JSOP_INSTANCEOF)
             if (!jsop_instanceof())
                 return Compile_Error;
           END_CASE(JSOP_INSTANCEOF)
 
           BEGIN_CASE(JSOP_EXCEPTION)
-          {
-            JS_STATIC_ASSERT(sizeof(cx->throwing) == 4);
-            RegisterID reg = frame.allocReg();
-            masm.loadPtr(FrameAddress(offsetof(VMFrame, cx)), reg);
-            masm.store32(Imm32(JS_FALSE), Address(reg, offsetof(JSContext, throwing)));
-
-            Address excn(reg, offsetof(JSContext, exception));
-            frame.freeReg(reg);
-            frame.push(excn);
-          }
+            prepareStubCall(Uses(0));
+            INLINE_STUBCALL(stubs::Exception);
+            frame.pushSynced();
           END_CASE(JSOP_EXCEPTION)
 
           BEGIN_CASE(JSOP_LINENO)
           END_CASE(JSOP_LINENO)
 
           BEGIN_CASE(JSOP_ENUMELEM)
             // Normally, SETELEM transforms the stack
             //  from: OBJ ID VALUE
--- a/js/src/methodjit/InvokeHelpers.cpp
+++ b/js/src/methodjit/InvokeHelpers.cpp
@@ -76,17 +76,17 @@ using namespace JSC;
 
 static jsbytecode *
 FindExceptionHandler(JSContext *cx)
 {
     JSStackFrame *fp = cx->fp();
     JSScript *script = fp->script();
 
 top:
-    if (cx->throwing && JSScript::isValidOffset(script->trynotesOffset)) {
+    if (cx->isExceptionPending() && JSScript::isValidOffset(script->trynotesOffset)) {
         // The PC is updated before every stub call, so we can use it here.
         unsigned offset = cx->regs->pc - script->main;
 
         JSTryNoteArray *tnarray = script->trynotes();
         for (unsigned i = 0; i < tnarray->length; ++i) {
             JSTryNote *tn = &tnarray->vector[i];
 
             // The following if condition actually tests two separate conditions:
@@ -114,56 +114,55 @@ top:
             JS_ASSERT(cx->regs->sp == fp->base() + tn->stackDepth);
 
             switch (tn->kind) {
                 case JSTRY_CATCH:
                   JS_ASSERT(js_GetOpcode(cx, fp->script(), pc) == JSOP_ENTERBLOCK);
 
 #if JS_HAS_GENERATORS
                   /* Catch cannot intercept the closing of a generator. */
-                  if (JS_UNLIKELY(cx->exception.isMagic(JS_GENERATOR_CLOSING)))
+                  if (JS_UNLIKELY(cx->getPendingException().isMagic(JS_GENERATOR_CLOSING)))
                       break;
 #endif
 
                   /*
                    * Don't clear cx->throwing to save cx->exception from GC
                    * until it is pushed to the stack via [exception] in the
                    * catch block.
                    */
                   return pc;
 
                 case JSTRY_FINALLY:
                   /*
                    * Push (true, exception) pair for finally to indicate that
                    * [retsub] should rethrow the exception.
                    */
                   cx->regs->sp[0].setBoolean(true);
-                  cx->regs->sp[1] = cx->exception;
+                  cx->regs->sp[1] = cx->getPendingException();
                   cx->regs->sp += 2;
-                  cx->throwing = JS_FALSE;
+                  cx->clearPendingException();
                   return pc;
 
                 case JSTRY_ITER:
                 {
                   /*
                    * This is similar to JSOP_ENDITER in the interpreter loop,
                    * except the code now uses the stack slot normally used by
                    * JSOP_NEXTITER, namely regs.sp[-1] before the regs.sp -= 2
                    * adjustment and regs.sp[1] after, to save and restore the
                    * pending exception.
                    */
-                  AutoValueRooter tvr(cx, cx->exception);
+                  Value v = cx->getPendingException();
                   JS_ASSERT(js_GetOpcode(cx, fp->script(), pc) == JSOP_ENDITER);
-                  cx->throwing = JS_FALSE;
+                  cx->clearPendingException();
                   ok = !!js_CloseIterator(cx, &cx->regs->sp[-1].toObject());
                   cx->regs->sp -= 1;
                   if (!ok)
                       goto top;
-                  cx->throwing = JS_TRUE;
-                  cx->exception = tvr.value();
+                  cx->setPendingException(v);
                 }
             }
         }
     }
 
     return NULL;
 }
 
@@ -497,27 +496,27 @@ js_InternalThrow(VMFrame &f)
 
     // Call the throw hook if necessary
     JSThrowHook handler = f.cx->debugHooks->throwHook;
     if (handler) {
         Value rval;
         switch (handler(cx, cx->fp()->script(), cx->regs->pc, Jsvalify(&rval),
                         cx->debugHooks->throwHookData)) {
           case JSTRAP_ERROR:
-            cx->throwing = JS_FALSE;
+            cx->clearPendingException();
             return NULL;
 
           case JSTRAP_RETURN:
-            cx->throwing = JS_FALSE;
+            cx->clearPendingException();
             cx->fp()->setReturnValue(rval);
             return JS_FUNC_TO_DATA_PTR(void *,
                    cx->jaegerCompartment()->forceReturnTrampoline());
 
           case JSTRAP_THROW:
-            cx->exception = rval;
+            cx->setPendingException(rval);
             break;
 
           default:
             break;
         }
     }
 
     jsbytecode *pc = NULL;
@@ -526,17 +525,17 @@ js_InternalThrow(VMFrame &f)
         if (pc)
             break;
 
         // If on the 'topmost' frame (where topmost means the first frame
         // called into through js_Interpret). In this case, we still unwind,
         // but we shouldn't return from a JS function, because we're not in a
         // JS function.
         bool lastFrame = (f.entryfp == f.fp());
-        js_UnwindScope(cx, 0, cx->throwing);
+        js_UnwindScope(cx, 0, cx->isExceptionPending());
 
         // For consistency with Interpret(), always run the script epilogue.
         // This simplifies interactions with RunTracer(), since it can assume
         // no matter how a function exited (error or not), that the epilogue
         // does not need to be run.
         ScriptEpilogue(f.cx, f.fp(), false);
 
         if (lastFrame)
@@ -653,31 +652,31 @@ HandleErrorInExcessFrame(VMFrame &f, JSS
         /* Clear imacros. */
         if (fp->hasImacropc()) {
             cx->regs->pc = fp->imacropc();
             fp->clearImacropc();
         }
         JS_ASSERT(!fp->hasImacropc());
 
         /* If there's an exception and a handler, set the pc and leave. */
-        if (cx->throwing) {
+        if (cx->isExceptionPending()) {
             jsbytecode *pc = FindExceptionHandler(cx);
             if (pc) {
                 cx->regs->pc = pc;
                 returnOK = true;
                 break;
             }
         }
 
         /* Don't unwind if this was the entry frame. */
         if (fp == stopFp)
             break;
 
         /* Unwind and return. */
-        returnOK &= bool(js_UnwindScope(cx, 0, returnOK || cx->throwing));
+        returnOK &= bool(js_UnwindScope(cx, 0, returnOK || cx->isExceptionPending()));
         returnOK = ScriptEpilogue(cx, fp, returnOK);
         InlineReturn(f);
     }
 
     JS_ASSERT(&f.regs == cx->regs);
     JS_ASSERT_IF(!returnOK, cx->fp() == stopFp);
 
     return returnOK;
@@ -968,17 +967,17 @@ RunTracer(VMFrame &f)
 
 #if JS_MONOIC
     if (blacklist)
         DisableTraceHint(f, tic);
 #endif
 
     // Even though ExecuteTree() bypasses the interpreter, it should propagate
     // error failures correctly.
-    JS_ASSERT_IF(cx->throwing, tpa == TPA_Error);
+    JS_ASSERT_IF(cx->isExceptionPending(), tpa == TPA_Error);
 
 	f.fp() = cx->fp();
     JS_ASSERT(f.fp() == cx->fp());
     switch (tpa) {
       case TPA_Nothing:
         return NULL;
 
       case TPA_Error:
--- a/js/src/methodjit/StubCalls.cpp
+++ b/js/src/methodjit/StubCalls.cpp
@@ -1286,34 +1286,33 @@ void JS_FASTCALL
 stubs::Debugger(VMFrame &f, jsbytecode *pc)
 {
     JSDebuggerHandler handler = f.cx->debugHooks->debuggerHandler;
     if (handler) {
         Value rval;
         switch (handler(f.cx, f.cx->fp()->script(), pc, Jsvalify(&rval),
                         f.cx->debugHooks->debuggerHandlerData)) {
           case JSTRAP_THROW:
-            f.cx->throwing = JS_TRUE;
-            f.cx->exception = rval;
+            f.cx->setPendingException(rval);
             THROW();
 
           case JSTRAP_RETURN:
-            f.cx->throwing = JS_FALSE;
+            f.cx->clearPendingException();
             f.cx->fp()->setReturnValue(rval);
 #if (defined(JS_NO_FASTCALL) && defined(JS_CPU_X86)) || defined(_WIN64)
             *f.returnAddressLocation() = JS_FUNC_TO_DATA_PTR(void *,
                                          f.cx->jaegerCompartment()->forceReturnFastTrampoline());
 #else
             *f.returnAddressLocation() = JS_FUNC_TO_DATA_PTR(void *,
                                          f.cx->jaegerCompartment()->forceReturnTrampoline());
 #endif
             break;
 
           case JSTRAP_ERROR:
-            f.cx->throwing = JS_FALSE;
+            f.cx->clearPendingException();
             THROW();
 
           default:
             break;
         }
     }
 }
 
@@ -1347,34 +1346,33 @@ stubs::Trap(VMFrame &f, uint32 trapTypes
                           f.cx->debugHooks->interruptHookData);
     }
 
     if (result == JSTRAP_CONTINUE && (trapTypes & JSTRAP_TRAP))
         result = JS_HandleTrap(f.cx, f.cx->fp()->script(), pc, Jsvalify(&rval));
 
     switch (result) {
       case JSTRAP_THROW:
-        f.cx->throwing = JS_TRUE;
-        f.cx->exception = rval;
+        f.cx->setPendingException(rval);
         THROW();
 
       case JSTRAP_RETURN:
-        f.cx->throwing = JS_FALSE;
+        f.cx->clearPendingException();
         f.cx->fp()->setReturnValue(rval);
 #if (defined(JS_NO_FASTCALL) && defined(JS_CPU_X86)) || defined(_WIN64)
         *f.returnAddressLocation() = JS_FUNC_TO_DATA_PTR(void *,
                                      f.cx->jaegerCompartment()->forceReturnFastTrampoline());
 #else
         *f.returnAddressLocation() = JS_FUNC_TO_DATA_PTR(void *,
                                      f.cx->jaegerCompartment()->forceReturnTrampoline());
 #endif
         break;
 
       case JSTRAP_ERROR:
-        f.cx->throwing = JS_FALSE;
+        f.cx->clearPendingException();
         THROW();
 
       default:
         break;
     }
 }
 
 void JS_FASTCALL
@@ -2330,19 +2328,18 @@ stubs::StrictNe(VMFrame &f)
     f.regs.sp[-1].setBoolean(equal != JS_TRUE);
 }
 
 void JS_FASTCALL
 stubs::Throw(VMFrame &f)
 {
     JSContext *cx = f.cx;
 
-    JS_ASSERT(!cx->throwing);
-    cx->throwing = JS_TRUE;
-    cx->exception = f.regs.sp[-1];
+    JS_ASSERT(!cx->isExceptionPending());
+    cx->setPendingException(f.regs.sp[-1]);
     THROW();
 }
 
 JSObject * JS_FASTCALL
 stubs::FlatLambda(VMFrame &f, JSFunction *fun)
 {
     JSObject *obj = js_NewFlatClosure(f.cx, fun, JSOP_LAMBDA_FC, JSOP_LAMBDA_FC_LENGTH);
     if (!obj)
@@ -2765,16 +2762,22 @@ stubs::In(VMFrame &f)
         THROWV(JS_FALSE);
 
     return !!prop;
 }
 
 template void JS_FASTCALL stubs::DelElem<true>(VMFrame &f);
 template void JS_FASTCALL stubs::DelElem<false>(VMFrame &f);
 
+void JS_FASTCALL
+stubs::Exception(VMFrame &f)
+{
+    f.regs.sp[0] = f.cx->getPendingException();
+    f.cx->clearPendingException();
+}
 template <bool Clamped>
 int32 JS_FASTCALL
 stubs::ConvertToTypedInt(JSContext *cx, Value *vp)
 {
     JS_ASSERT(!vp->isInt32());
 
     if (vp->isDouble()) {
         if (Clamped)
--- a/js/src/methodjit/StubCalls.h
+++ b/js/src/methodjit/StubCalls.h
@@ -219,16 +219,18 @@ JSString * JS_FASTCALL TypeOf(VMFrame &f
 JSBool JS_FASTCALL InstanceOf(VMFrame &f);
 void JS_FASTCALL FastInstanceOf(VMFrame &f);
 void JS_FASTCALL ArgCnt(VMFrame &f);
 void JS_FASTCALL Unbrand(VMFrame &f);
 
 template <bool strict> int32 JS_FASTCALL ConvertToTypedInt(JSContext *cx, Value *vp);
 void JS_FASTCALL ConvertToTypedFloat(JSContext *cx, Value *vp);
 
+void JS_FASTCALL Exception(VMFrame &f);
+
 } /* namespace stubs */
 
 /* 
  * If COND is true, return A; otherwise, return B. This allows us to choose between
  * function template instantiations without running afoul of C++'s overload resolution
  * rules. (Try simplifying, and you'll either see the problem --- or have found a
  * better solution!)
  */