Bug 1518661 - Part 4: Convert bytecode positions to be more expression-oriented. r=jimb,jorendorff
authorLogan Smyth <loganfsmyth@gmail.com>
Wed, 13 Feb 2019 02:31:00 +0000
changeset 458904 5add2761a3b6
parent 458903 9b872b266b2e
child 458905 5c934ede1cfc
push id35551
push usershindli@mozilla.com
push dateWed, 13 Feb 2019 21:34:09 +0000
treeherdermozilla-central@08f794a4928e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjimb, jorendorff
bugs1518661
milestone67.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 1518661 - Part 4: Convert bytecode positions to be more expression-oriented. r=jimb,jorendorff This brings SpiderMonkey more in line with V8 for the positions that it uses for expressions nested within statements. We generally prefer to use the expression's own location rather than the location of the statement, in the majority of cases. Differential Revision: https://phabricator.services.mozilla.com/D15993
devtools/client/debugger/new/test/mochitest/browser_dbg-pause-points.js
devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemapped-stepping.js
devtools/server/tests/unit/test_setBreakpoint-at-the-beginning-of-a-line.js
devtools/server/tests/unit/test_setBreakpoint-at-the-beginning-of-a-minified-fn.js
devtools/server/tests/unit/test_setBreakpoint-at-the-end-of-a-line.js
devtools/server/tests/unit/test_setBreakpoint-on-column-in-gcd-script.js
devtools/server/tests/unit/test_setBreakpoint-on-column.js
devtools/server/tests/unit/test_stepping-with-pause-points.js
devtools/server/tests/unit/test_stepping-with-skip-breakpoints.js
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/CallOrNewEmitter.cpp
js/src/jit-test/tests/debug/Script-getAllColumnOffsets.js
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-pause-points.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-pause-points.js
@@ -44,31 +44,31 @@ add_task(async function test() {
       [12, 2],
       [12, 12],
       [13, 0]
     ]
   });
 
   await testCase(dbg, {
     name: "expressions",
-    steps: [[40,2], [41,2], [41,8], [42,8], [43,0]]
+    steps: [[40,2], [41,2], [41,8], [42,12], [43,0]]
   });
 
   await testCase(dbg, {
     name: "sequences",
-    steps: [[23,2], [25,8], [29,8], [31,4], [34,2], [37,0]]
+    steps: [[23,2], [25,12], [31,4], [34,2], [37,0]]
   });
 
   await testCase(dbg, {
     name: "flow",
     steps: [
       [16, 2],
       [17, 12],
       [17, 20],
-      [18, 6],
+      [18, 10],
       [19, 2],
       [19, 8],
       [19, 17],
       [19, 25],
       [19, 8],
       [19, 17],
       [19, 25],
       [19, 8]
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemapped-stepping.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemapped-stepping.js
@@ -51,17 +51,17 @@ async function runSteps(dbg, source, ste
 
 function testStepOverForOf(dbg) {
   return breakpointSteps(
     dbg,
     "webpack3-babel6",
     "step-over-for-of",
     { line: 4, column: 2 },
     [
-      ["stepOver", { line: 6, column: 2 }],
+      ["stepOver", { line: 6, column: 20 }],
       ["stepOver", { line: 7, column: 4 }],
       ["stepOver", { line: 6, column: 2 }],
       ["stepOver", { line: 7, column: 4 }],
       ["stepOver", { line: 6, column: 2 }],
       ["stepOver", { line: 10, column: 2 }]
     ]
   );
 }
@@ -91,16 +91,17 @@ function testStepOverForOfArray(dbg) {
 // and Babel doesn't map the _loop() call, so we step past it automatically.
 function testStepOveForOfClosure(dbg) {
   return breakpointSteps(
     dbg,
     "webpack3-babel6",
     "step-over-for-of-closure",
     { line: 6, column: 2 },
     [
+      ["stepOver", { line: 8, column: 20 }],
       ["stepOver", { line: 8, column: 2 }],
       ["stepOver", { line: 12, column: 2 }]
     ]
   );
 }
 
 // Same as the previous, not possible to step into the body. The less
 // complicated array logic makes it possible to step into the header at least,
--- a/devtools/server/tests/unit/test_setBreakpoint-at-the-beginning-of-a-line.js
+++ b/devtools/server/tests/unit/test_setBreakpoint-at-the-beginning-of-a-line.js
@@ -17,17 +17,17 @@ add_task(threadClientTest(async ({ threa
   Assert.equal(packet.type, "paused");
   const why = packet.why;
   Assert.equal(why.type, "breakpoint");
   Assert.equal(why.actors.length, 1);
 
   const frame = packet.frame;
   Assert.equal(frame.where.actor, source.actor);
   Assert.equal(frame.where.line, location.line);
-  Assert.equal(frame.where.column, 6);
+  Assert.equal(frame.where.column, 10);
 
   const variables = frame.environment.bindings.variables;
   Assert.equal(variables.a.value.type, "undefined");
   Assert.equal(variables.b.value.type, "undefined");
   Assert.equal(variables.c.value.type, "undefined");
 
   await resume(threadClient);
 }, { doNotRunWorker: true }));
--- a/devtools/server/tests/unit/test_setBreakpoint-at-the-beginning-of-a-minified-fn.js
+++ b/devtools/server/tests/unit/test_setBreakpoint-at-the-beginning-of-a-minified-fn.js
@@ -20,17 +20,17 @@ add_task(threadClientTest(async ({ threa
   const why = packet.why;
   Assert.equal(why.type, "breakpoint");
   Assert.equal(why.actors.length, 1);
 
   const frame = packet.frame;
   const where = frame.where;
   Assert.equal(where.actor, source.actor);
   Assert.equal(where.line, location.line);
-  Assert.equal(where.column, 52);
+  Assert.equal(where.column, 56);
 
   const variables = frame.environment.bindings.variables;
   Assert.equal(variables.a.value.type, "undefined");
   Assert.equal(variables.b.value.type, "undefined");
   Assert.equal(variables.c.value.type, "undefined");
 
   await resume(threadClient);
 }, { doNotRunWorker: true }));
--- a/devtools/server/tests/unit/test_setBreakpoint-at-the-end-of-a-line.js
+++ b/devtools/server/tests/unit/test_setBreakpoint-at-the-end-of-a-line.js
@@ -18,17 +18,17 @@ add_task(threadClientTest(async ({ threa
   const why = packet.why;
   Assert.equal(why.type, "breakpoint");
   Assert.equal(why.actors.length, 1);
 
   const frame = packet.frame;
   const where = frame.where;
   Assert.equal(where.actor, source.actor);
   Assert.equal(where.line, location.line);
-  Assert.equal(where.column, 28);
+  Assert.equal(where.column, 32);
 
   const variables = frame.environment.bindings.variables;
   Assert.equal(variables.a.value, 1);
   Assert.equal(variables.b.value, 2);
   Assert.equal(variables.c.value.type, "undefined");
 
   await resume(threadClient);
 }, { doNotRunWorker: true }));
--- a/devtools/server/tests/unit/test_setBreakpoint-on-column-in-gcd-script.js
+++ b/devtools/server/tests/unit/test_setBreakpoint-on-column-in-gcd-script.js
@@ -4,17 +4,17 @@ const SOURCE_URL = getFileUrl("setBreakp
 
 add_task(threadClientTest(async ({ threadClient, debuggee, client, targetFront }) => {
   const promise = waitForNewSource(threadClient, SOURCE_URL);
   loadSubScriptWithOptions(SOURCE_URL, {target: debuggee, ignoreCache: true});
   Cu.forceGC(); Cu.forceGC(); Cu.forceGC();
 
   const { source } = await promise;
 
-  const location = { sourceUrl: source.url, line: 6, column: 17 };
+  const location = { sourceUrl: source.url, line: 6, column: 21 };
   setBreakpoint(threadClient, location);
 
   const packet = await executeOnNextTickAndWaitForPause(function() {
     reload(targetFront).then(function() {
       loadSubScriptWithOptions(SOURCE_URL, {target: debuggee, ignoreCache: true});
     });
   }, client);
   Assert.equal(packet.type, "paused");
--- a/devtools/server/tests/unit/test_setBreakpoint-on-column.js
+++ b/devtools/server/tests/unit/test_setBreakpoint-on-column.js
@@ -2,17 +2,17 @@
 
 const SOURCE_URL = getFileUrl("setBreakpoint-on-column.js");
 
 add_task(threadClientTest(async ({ threadClient, debuggee, client }) => {
   const promise = waitForNewSource(threadClient, SOURCE_URL);
   loadSubScript(SOURCE_URL, debuggee);
   const { source } = await promise;
 
-  const location = { sourceUrl: source.url, line: 4, column: 17 };
+  const location = { sourceUrl: source.url, line: 4, column: 21 };
   setBreakpoint(threadClient, location);
 
   const packet = await executeOnNextTickAndWaitForPause(function() {
     Cu.evalInSandbox("f()", debuggee);
   }, client);
   Assert.equal(packet.type, "paused");
   const why = packet.why;
   Assert.equal(why.type, "breakpoint");
--- a/devtools/server/tests/unit/test_stepping-with-pause-points.js
+++ b/devtools/server/tests/unit/test_stepping-with-pause-points.js
@@ -30,27 +30,27 @@ add_task(threadClientTest(async ({ threa
     types: {breakpoint: true, stepOver: true},
   }]);
 
   dumpn("Step Over to line 3");
   const step1 = await stepOver(client, threadClient);
   equal(step1.type, "paused");
   equal(step1.why.type, "resumeLimit");
   equal(step1.frame.where.line, 3);
-  equal(step1.frame.where.column, 8);
+  equal(step1.frame.where.column, 0);
 
   equal(debuggee.a, undefined);
   equal(debuggee.b, undefined);
 
   dumpn("Step Over to line 4");
   const step2 = await stepOver(client, threadClient);
   equal(step2.type, "paused");
   equal(step2.why.type, "resumeLimit");
   equal(step2.frame.where.line, 4);
-  equal(step2.frame.where.column, 8);
+  equal(step2.frame.where.column, 0);
 
   equal(debuggee.a, 1);
   equal(debuggee.b, undefined);
 
   dumpn("Step Over to the end of line 4");
   const step3 = await stepOver(client, threadClient);
   equal(step3.type, "paused");
   equal(step3.why.type, "resumeLimit");
--- a/devtools/server/tests/unit/test_stepping-with-skip-breakpoints.js
+++ b/devtools/server/tests/unit/test_stepping-with-skip-breakpoints.js
@@ -30,27 +30,27 @@ add_task(threadClientTest(async ({ threa
     types: {breakpoint: true, stepOver: true},
   }]);
 
   dumpn("Step Over to line 3");
   const step1 = await stepOver(client, threadClient);
   equal(step1.type, "paused");
   equal(step1.why.type, "resumeLimit");
   equal(step1.frame.where.line, 3);
-  equal(step1.frame.where.column, 8);
+  equal(step1.frame.where.column, 0);
 
   equal(debuggee.a, undefined);
   equal(debuggee.b, undefined);
 
   dumpn("Step Over to line 4");
   const step2 = await stepOver(client, threadClient);
   equal(step2.type, "paused");
   equal(step2.why.type, "resumeLimit");
   equal(step2.frame.where.line, 4);
-  equal(step2.frame.where.column, 8);
+  equal(step2.frame.where.column, 0);
 
   equal(debuggee.a, 1);
   equal(debuggee.b, undefined);
 
   dumpn("Step Over to the end of line 4");
   const step3 = await stepOver(client, threadClient);
   equal(step3.type, "paused");
   equal(step3.why.type, "resumeLimit");
@@ -68,9 +68,9 @@ function evaluateTestCode(debuggee) {
     var a = 1;                          // 3
     var b = 2;`,                        // 4
     debuggee,
     "1.8",
     "test_stepping-01-test-code.js",
     1
   );
   /* eslint-disable */
-}
+}
\ No newline at end of file
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -2004,17 +2004,17 @@ bool BytecodeEmitter::emitNumberOp(doubl
  */
 MOZ_NEVER_INLINE bool BytecodeEmitter::emitSwitch(SwitchStatement* switchStmt) {
   LexicalScopeNode& lexical = switchStmt->lexicalForCaseList();
   MOZ_ASSERT(lexical.isKind(ParseNodeKind::LexicalScope));
   ListNode* cases = &lexical.scopeBody()->as<ListNode>();
   MOZ_ASSERT(cases->isKind(ParseNodeKind::StatementList));
 
   SwitchEmitter se(this);
-  if (!se.emitDiscriminant(Some(switchStmt->pn_pos.begin))) {
+  if (!se.emitDiscriminant(Some(switchStmt->discriminant().pn_pos.begin))) {
     return false;
   }
   if (!emitTree(&switchStmt->discriminant())) {
     return false;
   }
 
   // Enter the scope before pushing the switch BreakableControl since all
   // breaks are under this scope.
@@ -3925,28 +3925,27 @@ bool BytecodeEmitter::emitTemplateString
 
   return true;
 }
 
 bool BytecodeEmitter::emitDeclarationList(ListNode* declList) {
   MOZ_ASSERT(declList->isOp(JSOP_NOP));
 
   for (ParseNode* decl : declList->contents()) {
-    if (!updateSourceCoordNotes(decl->pn_pos.begin)) {
-      return false;
-    }
-
     if (decl->isKind(ParseNodeKind::AssignExpr)) {
       MOZ_ASSERT(decl->isOp(JSOP_NOP));
 
       AssignmentNode* assignNode = &decl->as<AssignmentNode>();
       ListNode* pattern = &assignNode->left()->as<ListNode>();
       MOZ_ASSERT(pattern->isKind(ParseNodeKind::ArrayExpr) ||
                  pattern->isKind(ParseNodeKind::ObjectExpr));
 
+      if (!updateSourceCoordNotes(assignNode->right()->pn_pos.begin)) {
+        return false;
+      }
       if (!emitTree(assignNode->right())) {
         return false;
       }
 
       if (!emitDestructuringOps(pattern, DestructuringDeclaration)) {
         return false;
       }
 
@@ -3984,16 +3983,20 @@ bool BytecodeEmitter::emitSingleDeclarat
                "var declarations without initializers handled above, "
                "and const declarations must have initializers");
     if (!emit1(JSOP_UNDEFINED)) {
       //            [stack] ENV? UNDEF
       return false;
     }
   } else {
     MOZ_ASSERT(initializer);
+
+    if (!updateSourceCoordNotes(initializer->pn_pos.begin)) {
+      return false;
+    }
     if (!emitInitializer(initializer, decl)) {
       //            [stack] ENV? V
       return false;
     }
   }
   if (!noe.emitAssignment()) {
     //              [stack] V
     return false;
@@ -4638,17 +4641,17 @@ MOZ_MUST_USE bool BytecodeEmitter::emitG
 
   SET_RESUMEINDEX(code(off), resumeIndex);
   return true;
 }
 
 bool BytecodeEmitter::emitIf(TernaryNode* ifNode) {
   IfEmitter ifThenElse(this);
 
-  if (!ifThenElse.emitIf(Some(ifNode->pn_pos.begin))) {
+  if (!ifThenElse.emitIf(Some(ifNode->kid1()->pn_pos.begin))) {
     return false;
   }
 
 if_again:
   /* Emit code for the condition before pushing stmtInfo. */
   if (!emitTree(ifNode->kid1())) {
     return false;
   }
@@ -4668,17 +4671,17 @@ if_again:
   if (!emitTree(ifNode->kid2())) {
     return false;
   }
 
   if (elseNode) {
     if (elseNode->isKind(ParseNodeKind::IfStmt)) {
       ifNode = &elseNode->as<TernaryNode>();
 
-      if (!ifThenElse.emitElseIf(Some(ifNode->pn_pos.begin))) {
+      if (!ifThenElse.emitElseIf(Some(ifNode->kid1()->pn_pos.begin))) {
         return false;
       }
 
       goto if_again;
     }
 
     if (!ifThenElse.emitElse()) {
       return false;
@@ -4807,17 +4810,17 @@ MOZ_NEVER_INLINE bool BytecodeEmitter::e
   if (!lse.emitEnd()) {
     return false;
   }
   return true;
 }
 
 bool BytecodeEmitter::emitWith(BinaryNode* withNode) {
   // Ensure that the column of the 'with' is set properly.
-  if (!updateSourceCoordNotes(withNode->pn_pos.begin)) {
+  if (!updateSourceCoordNotes(withNode->left()->pn_pos.begin)) {
     return false;
   }
 
   if (!emitTree(withNode->left())) {
     return false;
   }
 
   EmitterScope emitterScope(this);
@@ -5265,16 +5268,19 @@ bool BytecodeEmitter::emitForOf(ForNode*
   ForOfEmitter forOf(this, headLexicalEmitterScope, allowSelfHostedIter,
                      iterKind);
 
   if (!forOf.emitIterated()) {
     //              [stack]
     return false;
   }
 
+  if (!updateSourceCoordNotes(forHeadExpr->pn_pos.begin)) {
+    return false;
+  }
   if (!emitTree(forHeadExpr)) {
     //              [stack] ITERABLE
     return false;
   }
 
   if (headLexicalEmitterScope) {
     DebugOnly<ParseNode*> forOfTarget = forOfHead->kid1();
     MOZ_ASSERT(forOfTarget->isKind(ParseNodeKind::LetDecl) ||
@@ -5359,16 +5365,20 @@ bool BytecodeEmitter::emitForIn(ForNode*
 
   if (!forIn.emitIterated()) {
     //              [stack]
     return false;
   }
 
   // Evaluate the expression being iterated.
   ParseNode* expr = forInHead->kid3();
+
+  if (!updateSourceCoordNotes(expr->pn_pos.begin)) {
+    return false;
+  }
   if (!emitTree(expr)) {
     //              [stack] EXPR
     return false;
   }
 
   MOZ_ASSERT(forInLoop->iflags() == 0);
 
   MOZ_ASSERT_IF(headLexicalEmitterScope,
@@ -5431,16 +5441,20 @@ bool BytecodeEmitter::emitCStyleFor(
     // declaration. (The loop variables were hoisted into an enclosing
     // scope, but we still need to emit code for the initializers.)
     if (init->isForLoopDeclaration()) {
       if (!emitTree(init)) {
         //          [stack]
         return false;
       }
     } else {
+      if (!updateSourceCoordNotes(init->pn_pos.begin)) {
+        return false;
+      }
+
       // 'init' is an expression, not a declaration. emitTree left its
       // value on the stack.
       if (!emitTree(init, ValueUsage::IgnoreValue)) {
         //          [stack] VAL
         return false;
       }
       if (!emit1(JSOP_POP)) {
         //          [stack]
@@ -5465,30 +5479,36 @@ bool BytecodeEmitter::emitCStyleFor(
           update ? CForEmitter::Update::Present : CForEmitter::Update::Missing,
           update ? Some(update->pn_pos.begin) : Nothing())) {
     //              [stack]
     return false;
   }
 
   // Check for update code to do before the condition (if any).
   if (update) {
+    if (!updateSourceCoordNotes(update->pn_pos.begin)) {
+      return false;
+    }
     if (!emitTree(update, ValueUsage::IgnoreValue)) {
       //            [stack] VAL
       return false;
     }
   }
 
   if (!cfor.emitCond(Some(forNode->pn_pos.begin),
                      cond ? Some(cond->pn_pos.begin) : Nothing(),
                      Some(forNode->pn_pos.end))) {
     //              [stack]
     return false;
   }
 
   if (cond) {
+    if (!updateSourceCoordNotes(cond->pn_pos.begin)) {
+      return false;
+    }
     if (!emitTree(cond)) {
       //            [stack] VAL
       return false;
     }
   }
 
   if (!cfor.emitEnd()) {
     //              [stack]
@@ -5840,16 +5860,19 @@ bool BytecodeEmitter::emitDo(BinaryNode*
     return false;
   }
 
   if (!doWhile.emitCond()) {
     return false;
   }
 
   ParseNode* condNode = doNode->right();
+  if (!updateSourceCoordNotes(condNode->pn_pos.begin)) {
+    return false;
+  }
   if (!emitTree(condNode)) {
     return false;
   }
 
   if (!doWhile.emitEnd()) {
     return false;
   }
 
@@ -5869,16 +5892,19 @@ bool BytecodeEmitter::emitWhile(BinaryNo
     return false;
   }
 
   ParseNode* condNode = whileNode->left();
   if (!wh.emitCond(getOffsetForLoop(condNode))) {
     return false;
   }
 
+  if (!updateSourceCoordNotes(condNode->pn_pos.begin)) {
+    return false;
+  }
   if (!emitTree(condNode)) {
     return false;
   }
 
   if (!wh.emitEnd()) {
     return false;
   }
 
@@ -5997,16 +6023,20 @@ bool BytecodeEmitter::emitReturn(UnaryNo
   bool needsIteratorResult =
       sc->isFunctionBox() && sc->asFunctionBox()->needsIteratorResult();
   if (needsIteratorResult) {
     if (!emitPrepareIteratorResult()) {
       return false;
     }
   }
 
+  if (!updateSourceCoordNotes(returnNode->pn_pos.begin)) {
+    return false;
+  }
+
   /* Push a return value */
   if (ParseNode* expr = returnNode->kid()) {
     if (!emitTree(expr)) {
       return false;
     }
 
     bool isAsyncGenerator =
         sc->asFunctionBox()->isAsync() && sc->asFunctionBox()->isGenerator();
@@ -8552,16 +8582,19 @@ bool BytecodeEmitter::emitClass(
 
   // This is kind of silly. In order to the get the home object defined on
   // the constructor, we have to make it second, but we want the prototype
   // on top for EmitPropertyList, because we expect static properties to be
   // rarer. The result is a few more swaps than we would like. Such is life.
   bool isDerived = !!heritageExpression;
   bool hasNameOnStack = nameKind == ClassNameKind::ComputedName;
   if (isDerived) {
+    if (!updateSourceCoordNotes(classNode->pn_pos.begin)) {
+      return false;
+    }
     if (!emitTree(heritageExpression)) {
       //            [stack] HERITAGE
       return false;
     }
     if (!ce.emitDerivedClass(innerName, nameForAnonymousClass,
                              hasNameOnStack)) {
       //            [stack] HERITAGE HOMEOBJ
       return false;
@@ -8896,16 +8929,20 @@ bool BytecodeEmitter::emitTree(
 
     case ParseNodeKind::TypeOfExpr:
       if (!emitTypeof(&pn->as<UnaryNode>(), JSOP_TYPEOFEXPR)) {
         return false;
       }
       break;
 
     case ParseNodeKind::ThrowStmt:
+      if (!updateSourceCoordNotes(pn->pn_pos.begin)) {
+        return false;
+      }
+      MOZ_FALLTHROUGH;
     case ParseNodeKind::VoidExpr:
     case ParseNodeKind::NotExpr:
     case ParseNodeKind::BitNotExpr:
     case ParseNodeKind::PosExpr:
     case ParseNodeKind::NegExpr:
       if (!emitUnary(&pn->as<UnaryNode>())) {
         return false;
       }
--- a/js/src/frontend/CallOrNewEmitter.cpp
+++ b/js/src/frontend/CallOrNewEmitter.cpp
@@ -266,27 +266,27 @@ bool CallOrNewEmitter::emitEnd(uint32_t 
       // Repush the callee as new.target
       uint32_t effectiveArgc = isSpread() ? 1 : argc;
       if (!bce_->emitDupAt(effectiveArgc + 1)) {
         //          [stack] CALLEE THIS ARR CALLEE
         return false;
       }
     }
   }
+  if (beginPos) {
+    if (!bce_->updateSourceCoordNotes(*beginPos)) {
+      return false;
+    }
+  }
   if (!isSpread()) {
-    if (!bce_->emitCall(op_, argc, beginPos)) {
+    if (!bce_->emitCall(op_, argc)) {
       //            [stack] RVAL
       return false;
     }
   } else {
-    if (beginPos) {
-      if (!bce_->updateSourceCoordNotes(*beginPos)) {
-        return false;
-      }
-    }
     if (!bce_->emit1(op_)) {
       //            [stack] RVAL
       return false;
     }
   }
 
   if (isEval() && beginPos) {
     uint32_t lineNum = bce_->parser->errorReporter().lineAt(*beginPos);
--- a/js/src/jit-test/tests/debug/Script-getAllColumnOffsets.js
+++ b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets.js
@@ -1,92 +1,93 @@
 load(libdir + "assert-offset-columns.js");
 
 // getColumnOffsets correctly places the various parts of a ForStatement.
 assertOffsetColumns(
     "function f(n) { for (var i = 0; i < n; ++i) hits.push('.'); hits.push('!'); }",
-    "                         ^      ^      ^    ^    ^          ^    ^          ^",
+    "                             ^  ^      ^    ^    ^          ^    ^          ^",
     "0 1 3 4 . 2 1 3 4 . 2 1 3 4 . 2 1 5 6 ! 7",
 );
 
 // getColumnOffsets correctly places multiple variable declarations.
 assertOffsetColumns(
     "function f(n){var w0,x1=3,y2=4,z3=9}",
-    "                     ^    ^    ^   ^",
+    "                        ^    ^    ^^",
 );
 
 // getColumnOffsets correctly places comma separated expressions.
 assertOffsetColumns(
     "function f(n){print(n),print(n),print(n)}",
     "              ^        ^        ^       ^",
 );
 
 // getColumnOffsets correctly places object properties.
 assertOffsetColumns(
     // Should hit each property in the object.
     "function f(n){var o={a:1,b:2,c:3}}",
-    "                  ^  ^   ^   ^   ^",
+    "                    ^^   ^   ^   ^",
 );
 
 // getColumnOffsets correctly places array properties.
 assertOffsetColumns(
     // Should hit each item in the array.
     "function f(n){var a=[1,2,n]}",
-    "                  ^  ^ ^ ^ ^",
+    "                    ^^ ^ ^ ^",
 );
 
 // getColumnOffsets correctly places function calls.
 assertOffsetColumns(
     "function ppppp() { return 1; }\n" +
     "function f(){ 1 && ppppp(ppppp()) && new Error() }",
     "              ^    ^     ^           ^           ^",
     "0 2 1 3 4",
 );
 
 // getColumnOffsets correctly places the various parts of a SwitchStatement.
 assertOffsetColumns(
     "function f(n) { switch(n) { default: print(n); } }",
-    "                ^                    ^           ^",
+    "                       ^             ^           ^",
 );
 
 // getColumnOffsets correctly places the various parts of a BreakStatement.
 assertOffsetColumns(
     "function f(n) { do { print(n); break; } while(false); }",
-    "                ^    ^         ^                      ^",
+    "                ^    ^         ^              ^       ^",
+    "0 1 2 4"
 );
 
 // getColumnOffsets correctly places the various parts of a ContinueStatement.
 assertOffsetColumns(
     "function f(n) { do { print(n); continue; } while(false); }",
-    "                ^    ^         ^                         ^",
+    "                ^    ^         ^                 ^       ^",
 );
 
 // getColumnOffsets correctly places the various parts of a WithStatement.
 assertOffsetColumns(
     "function f(n) { with({}) { print(n); } }",
-    "                ^          ^           ^",
+    "                     ^     ^           ^",
 );
 
 // getColumnOffsets correctly places the various parts of a IfStatement.
 assertOffsetColumns(
     "function f(n) { if (n == 3) print(n); }",
-    "                ^           ^         ^",
+    "                    ^       ^         ^",
 );
 
 // getColumnOffsets correctly places the various parts of a IfStatement
 // with an if/else
 assertOffsetColumns(
     "function f(n) { if (n == 2); else if (n === 3) print(n); }",
-    "                ^                 ^            ^         ^",
+    "                    ^                 ^        ^         ^",
 );
 
 // getColumnOffsets correctly places the various parts of a DoWhileStatement.
 assertOffsetColumns(
     "function f(n) { do { print(n); } while(false); }",
-    "                ^    ^                         ^",
+    "                ^    ^                 ^       ^",
 );
 
 // getColumnOffsets correctly places the part of normal ::Dot node with identifier root.
 assertOffsetColumns(
     "var args = [];\n" +
     "var obj = { base: { a(){ return { b(){} }; } } };\n" +
     "function f(n) { obj.base.a().b(...args); }",
     "                ^        ^   ^ ^         ^",