Bug 832373 part 3 - Fix eval-in-frame to work with baseline frames. r=luke
authorJan de Mooij <jdemooij@mozilla.com>
Tue, 22 Jan 2013 18:02:50 +0100
changeset 119611 7e34528bc2b7c79671b883da02a216238b1cc1be
parent 119610 648791c1fd991a9f6ea72b9f06b05c88c52b5557
child 119612 e48f83e2aee52c5a116fdedca9527b25e174e2be
push id1441
push userjandemooij@gmail.com
push dateTue, 22 Jan 2013 17:04:28 +0000
reviewersluke
bugs832373
milestone21.0a1
Bug 832373 part 3 - Fix eval-in-frame to work with baseline frames. r=luke
js/src/jit-test/tests/debug/Frame-eval-stack.js
js/src/vm/Stack.cpp
js/src/vm/Stack.h
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-stack.js
@@ -0,0 +1,19 @@
+var g = newGlobal();
+var dbg = new Debugger(g);
+
+g.eval("function h() { debugger; }");
+g.eval("function g() { h() }");
+g.eval("function f() { var blah = 333; g() }");
+
+dbg.onDebuggerStatement = function(frame) {
+    frame = frame.older;
+    g.trace = frame.older.eval("(new Error()).stack;").return;
+}
+g.f();
+
+assertEq(typeof g.trace, "string");
+
+var frames = g.trace.split("\n");
+assertEq(frames[0].contains("eval code"), true);
+assertEq(frames[1].startsWith("f@"), true);
+assertEq(frames[2].startsWith("@"), true);
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -90,16 +90,22 @@ StackFrame::initExecuteFrame(UnrootedScr
     }
 
     scopeChain_ = &scopeChain;
     prev_ = prevLink;
     prevpc_ = regs ? regs->pc : (jsbytecode *)0xbad;
     prevInline_ = regs ? regs->inlined() : NULL;
     blockChain_ = NULL;
 
+#ifdef JS_ION
+    /* Set prevBaselineFrame_ if this is an eval-in-frame. */
+    prevBaselineFrame_ = prev.isBaselineFrame() ? prev.asBaselineFrame() : NULL;
+    JS_ASSERT_IF(prevBaselineFrame_, isDebuggerFrame());
+#endif
+
 #ifdef DEBUG
     ncode_ = (void *)0xbad;
     Debug_SetValueRangeToCrashOnTouch(&rval_, 1);
     hookData_ = (void *)0xbad;
 #endif
 }
 
 template <StackFrame::TriggerPostBarriers doPostBarrier>
@@ -1076,29 +1082,29 @@ ContextStack::pushExecuteFrame(JSContext
      * frame.
      */
     CallArgsList *evalInFrameCalls = NULL;  /* quell overwarning */
     MaybeExtend extend;
     StackFrame *prevLink;
     if (evalInFrame) {
         /* First, find the right segment. */
         AllFramesIter frameIter(cx->runtime);
-        while (frameIter.isIon() || frameIter.abstractFramePtr() != evalInFrame)
+        while (frameIter.isIonOptimizedJS() || frameIter.abstractFramePtr() != evalInFrame)
             ++frameIter;
         JS_ASSERT(frameIter.abstractFramePtr() == evalInFrame);
 
         StackSegment &seg = *frameIter.seg();
 
         StackIter iter(cx->runtime, seg);
         /* Debug-mode currently disables Ion compilation. */
         JS_ASSERT_IF(evalInFrame.isStackFrame(), !evalInFrame.asStackFrame()->runningInIon());
-        JS_ASSERT_IF(evalInFrame.compartment() == iter.compartment(), !iter.isIon());
-        while (!iter.isScript() || iter.isIon() || iter.abstractFramePtr() != evalInFrame) {
+        JS_ASSERT_IF(evalInFrame.compartment() == iter.compartment(), !iter.isIonOptimizedJS());
+        while (!iter.isScript() || iter.isIonOptimizedJS() || iter.abstractFramePtr() != evalInFrame) {
             ++iter;
-            JS_ASSERT_IF(evalInFrame.compartment() == iter.compartment(), !iter.isIon());
+            JS_ASSERT_IF(evalInFrame.compartment() == iter.compartment(), !iter.isIonOptimizedJS());
         }
         JS_ASSERT(iter.abstractFramePtr() == evalInFrame);
         evalInFrameCalls = iter.data_.calls_;
         prevLink = iter.data_.fp_;
         extend = CANT_EXTEND;
     } else {
         prevLink = maybefp();
         extend = CAN_EXTEND;
@@ -1393,16 +1399,33 @@ StackIter::settleOnNewState()
 
         /*
          * In case of both a scripted frame and call record, use linear memory
          * ordering to decide which was the most recent.
          */
         if (containsFrame && (!containsCall || (Value *)data_.fp_ >= data_.calls_->array())) {
 #ifdef JS_ION
             if (data_.fp_->beginsIonActivation()) {
+                /*
+                 * Eval-in-frame can link to an arbitrary frame on the stack.
+                 * Skip any IonActivation's until we reach the one for the
+                 * current StackFrame. Treat activations with NULL entryfp
+                 * (pushed by FastInvoke) as belonging to the previous
+                 * activation.
+                 */
+                while (true) {
+                    ion::IonActivation *act = data_.ionActivations_.activation();
+                    while (!act->entryfp())
+                        act = act->prev();
+                    if (act->entryfp() == data_.fp_)
+                        break;
+
+                    ++data_.ionActivations_;
+                }
+
                 data_.ionFrames_ = ion::IonFrameIterator(data_.ionActivations_);
 
                 if (data_.ionFrames_.isNative()) {
                     data_.state_ = ION;
                     return;
                 }
 
                 while (!data_.ionFrames_.isScripted() && !data_.ionFrames_.done())
@@ -1591,25 +1614,46 @@ StackIter::popIonFrame()
         } else {
             JS_ASSERT(data_.fp_->callingIntoIon());
             data_.state_ = SCRIPTED;
             data_.pc_ = data_.ionActivations_.activation()->prevpc();
             ++data_.ionActivations_;
         }
     }
 }
+
+void
+StackIter::popBaselineDebuggerFrame()
+{
+    ion::BaselineFrame *prevBaseline = data_.fp_->prevBaselineFrame();
+
+    popFrame();
+    settleOnNewState();
+
+    /* Pop Ion frames until we reach the target frame. */
+    JS_ASSERT(data_.state_ == ION);
+    while (!data_.ionFrames_.isBaselineJS() || data_.ionFrames_.baselineFrame() != prevBaseline)
+        popIonFrame();
+}
 #endif
 
 StackIter &
 StackIter::operator++()
 {
     switch (data_.state_) {
       case DONE:
         JS_NOT_REACHED("Unexpected state");
       case SCRIPTED:
+#ifdef JS_ION
+        if (data_.fp_->isDebuggerFrame() && data_.fp_->prevBaselineFrame()) {
+            /* Eval-in-frame with a baseline JIT frame. */
+            popBaselineDebuggerFrame();
+            break;
+        }
+#endif
         popFrame();
         settleOnNewState();
         break;
       case NATIVE:
         popCall();
         settleOnNewState();
         break;
       case ION:
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -408,16 +408,19 @@ class StackFrame
     Value               rval_;          /* if HAS_RVAL, return value of the frame */
     StaticBlockObject   *blockChain_;   /* if HAS_BLOCKCHAIN, innermost let block */
     ArgumentsObject     *argsObj_;      /* if HAS_ARGS_OBJ, the call's arguments object */
     jsbytecode          *prevpc_;       /* if HAS_PREVPC, pc of previous frame*/
     InlinedSite         *prevInline_;   /* for a jit frame, inlined site in previous frame */
     void                *hookData_;     /* if HAS_HOOK_DATA, closure returned by call hook */
     FrameRejoinState    rejoin_;        /* for a jit frame rejoining the interpreter
                                          * from JIT code, state at rejoin. */
+#ifdef JS_ION
+    ion::BaselineFrame  *prevBaselineFrame_; /* for a debugger frame, the baseline frame to use as prev. */
+#endif
 
     static void staticAsserts() {
         JS_STATIC_ASSERT(offsetof(StackFrame, rval_) % sizeof(Value) == 0);
         JS_STATIC_ASSERT(sizeof(StackFrame) % sizeof(Value) == 0);
     }
 
     inline void initPrev(JSContext *cx);
     jsbytecode *prevpcSlow(InlinedSite **pinlined);
@@ -574,16 +577,29 @@ class StackFrame
      * 'prev' has little semantic meaning and basically just tells the VM what
      * to set cx->regs->fp to when this frame is popped.
      */
 
     StackFrame *prev() const {
         return prev_;
     }
 
+#ifdef JS_ION
+    /*
+     * To handle eval-in-frame with a baseline JIT frame, |prev_| points to the
+     * entry frame and prevBaselineFrame_ to the actual BaselineFrame. This is
+     * done so that StackIter can skip JIT frames pushed on top of the baseline
+     * frame (these frames should not appear in stack traces).
+     */
+    ion::BaselineFrame *prevBaselineFrame() const {
+        JS_ASSERT(isDebuggerFrame());
+        return prevBaselineFrame_;
+    }
+#endif
+
     inline void resetGeneratorPrev(JSContext *cx);
 
     /*
      * (Unaliased) locals and arguments
      *
      * Only non-eval function frames have arguments. The arguments pushed by
      * the caller are the 'actual' arguments. The declared arguments of the
      * callee are the 'formal' arguments. When the caller passes less or equal
@@ -1894,16 +1910,17 @@ class StackIter
 #endif
 
     void poisonRegs();
     void popFrame();
     void popCall();
 #ifdef JS_ION
     void nextIonFrame();
     void popIonFrame();
+    void popBaselineDebuggerFrame();
 #endif
     void settleOnNewSegment();
     void settleOnNewState();
     void startOnSegment(StackSegment *seg);
 
   public:
     StackIter(JSContext *cx, SavedOption = STOP_AT_SAVED);
     StackIter(JSRuntime *rt, StackSegment &seg);