Bug 568142 - Part 1: Add column numbers to error reports. r=jorendorff
authorAlex Crichton <acrichton@mozilla.com>
Wed, 08 Aug 2012 11:39:40 -0700
changeset 105085 753d5e8c80640b35cd5bfd1b2b1cd8d009ac56ef
parent 105084 bf07c6253287bb55cb346002959048c15822ed67
child 105086 a8601aeb1d1d1cb838832fc15cd4d6d0653a6448
push id55
push usershu@rfrn.org
push dateThu, 30 Aug 2012 01:33:09 +0000
reviewersjorendorff
bugs568142
milestone17.0a1
Bug 568142 - Part 1: Add column numbers to error reports. r=jorendorff
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/jit-test/jit_test.py
js/src/jsapi-tests/Makefile.in
js/src/jsapi-tests/testErrorCopying.cpp
js/src/jsapi.h
js/src/jsatom.tbl
js/src/jscntxt.cpp
js/src/jsexn.cpp
js/src/jsscript.cpp
js/src/jsscript.h
js/src/shell/js.cpp
js/src/tests/ecma/extensions/errorcolumnblame.js
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -381,82 +381,99 @@ EmitBackPatchOp(JSContext *cx, BytecodeE
 
     offset = bce->offset();
     delta = offset - *lastp;
     *lastp = offset;
     JS_ASSERT(delta > 0);
     return EmitJump(cx, bce, op, delta);
 }
 
-/* A macro for inlining at the top of EmitTree (whence it came). */
-#define UPDATE_LINE_NUMBER_NOTES(cx, bce, line)                               \
-    JS_BEGIN_MACRO                                                            \
-        unsigned line_ = (line);                                                 \
-        unsigned delta_ = line_ - bce->currentLine();                            \
-        if (delta_ != 0) {                                                    \
-            /*                                                                \
-             * Encode any change in the current source line number by using   \
-             * either several SRC_NEWLINE notes or just one SRC_SETLINE note, \
-             * whichever consumes less space.                                 \
-             *                                                                \
-             * NB: We handle backward line number deltas (possible with for   \
-             * loops where the update part is emitted after the body, but its \
-             * line number is <= any line number in the body) here by letting \
-             * unsigned delta_ wrap to a very large number, which triggers a  \
-             * SRC_SETLINE.                                                   \
-             */                                                               \
-            bce->current->currentLine = line_;                                \
-            if (delta_ >= (unsigned)(2 + ((line_ > SN_3BYTE_OFFSET_MASK)<<1))) { \
-                if (NewSrcNote2(cx, bce, SRC_SETLINE, (ptrdiff_t)line_) < 0)  \
-                    return false;                                             \
-            } else {                                                          \
-                do {                                                          \
-                    if (NewSrcNote(cx, bce, SRC_NEWLINE) < 0)                 \
-                        return false;                                         \
-                } while (--delta_ != 0);                                      \
-            }                                                                 \
-        }                                                                     \
-    JS_END_MACRO
+/* Updates line number notes, not column notes. */
+static inline bool
+UpdateLineNumberNotes(JSContext *cx, BytecodeEmitter *bce, unsigned line)
+{
+    unsigned delta = line - bce->currentLine();
+    if (delta != 0) {
+        /*
+         * Encode any change in the current source line number by using
+         * either several SRC_NEWLINE notes or just one SRC_SETLINE note,
+         * whichever consumes less space.
+         *
+         * NB: We handle backward line number deltas (possible with for
+         * loops where the update part is emitted after the body, but its
+         * line number is <= any line number in the body) here by letting
+         * unsigned delta_ wrap to a very large number, which triggers a
+         * SRC_SETLINE.
+         */
+        bce->current->currentLine = line;
+        bce->current->lastColumn  = 0;
+        if (delta >= (unsigned)(2 + ((line > SN_3BYTE_OFFSET_MASK)<<1))) {
+            if (NewSrcNote2(cx, bce, SRC_SETLINE, (ptrdiff_t)line) < 0)
+                return false;
+        } else {
+            do {
+                if (NewSrcNote(cx, bce, SRC_NEWLINE) < 0)
+                    return false;
+            } while (--delta != 0);
+        }
+    }
+    return true;
+}
 
 /* A function, so that we avoid macro-bloating all the other callsites. */
 static bool
-UpdateLineNumberNotes(JSContext *cx, BytecodeEmitter *bce, unsigned line)
+UpdateSourceCoordNotes(JSContext *cx, BytecodeEmitter *bce, TokenPtr pos)
 {
-    UPDATE_LINE_NUMBER_NOTES(cx, bce, line);
+    if (!UpdateLineNumberNotes(cx, bce, pos.lineno))
+        return false;
+
+    ptrdiff_t colspan = ptrdiff_t(pos.index) -
+                        ptrdiff_t(bce->current->lastColumn);
+    if (colspan != 0) {
+        if (colspan < 0) {
+            colspan += SN_COLSPAN_DOMAIN;
+        } else if (colspan >= SN_COLSPAN_DOMAIN / 2) {
+            ReportStatementTooLarge(cx, bce->topStmt);
+            return false;
+        }
+        if (NewSrcNote2(cx, bce, SRC_COLSPAN, colspan) < 0)
+            return false;
+        bce->current->lastColumn = pos.index;
+    }
     return true;
 }
 
 static ptrdiff_t
 EmitLoopHead(JSContext *cx, BytecodeEmitter *bce, ParseNode *nextpn)
 {
     if (nextpn) {
         /*
          * Try to give the JSOP_LOOPHEAD the same line number as the next
          * instruction. nextpn is often a block, in which case the next
          * instruction typically comes from the first statement inside.
          */
         JS_ASSERT_IF(nextpn->isKind(PNK_STATEMENTLIST), nextpn->isArity(PN_LIST));
         if (nextpn->isKind(PNK_STATEMENTLIST) && nextpn->pn_head)
             nextpn = nextpn->pn_head;
-        if (!UpdateLineNumberNotes(cx, bce, nextpn->pn_pos.begin.lineno))
+        if (!UpdateSourceCoordNotes(cx, bce, nextpn->pn_pos.begin))
             return -1;
     }
 
     return Emit1(cx, bce, JSOP_LOOPHEAD);
 }
 
 static bool
 EmitLoopEntry(JSContext *cx, BytecodeEmitter *bce, ParseNode *nextpn)
 {
     if (nextpn) {
         /* Update the line number, as for LOOPHEAD. */
         JS_ASSERT_IF(nextpn->isKind(PNK_STATEMENTLIST), nextpn->isArity(PN_LIST));
         if (nextpn->isKind(PNK_STATEMENTLIST) && nextpn->pn_head)
             nextpn = nextpn->pn_head;
-        if (!UpdateLineNumberNotes(cx, bce, nextpn->pn_pos.begin.lineno))
+        if (!UpdateSourceCoordNotes(cx, bce, nextpn->pn_pos.begin))
             return false;
     }
 
     return Emit1(cx, bce, JSOP_LOOPENTRY) >= 0;
 }
 
 /*
  * If op is JOF_TYPESET (see the type barriers comment in jsinfer.h), reserve
@@ -2685,17 +2702,17 @@ MaybeEmitVarDecl(JSContext *cx, Bytecode
         if (!bce->makeAtomIndex(pn->pn_atom, &atomIndex))
             return false;
     }
 
     if (JOF_OPTYPE(pn->getOp()) == JOF_ATOM &&
         (!bce->sc->inFunction() || bce->sc->fun()->isHeavyweight()))
     {
         bce->switchToProlog();
-        if (!UpdateLineNumberNotes(cx, bce, pn->pn_pos.begin.lineno))
+        if (!UpdateSourceCoordNotes(cx, bce, pn->pn_pos.begin))
             return false;
         if (!EmitIndexOp(cx, prologOp, atomIndex, bce))
             return false;
         bce->switchToMain();
     }
 
     if (result)
         *result = atomIndex;
@@ -4100,17 +4117,17 @@ EmitTry(JSContext *cx, BytecodeEmitter *
          */
         if (!BackPatch(cx, bce, stmtInfo.gosubs(), bce->next(), JSOP_GOSUB))
             return false;
 
         finallyStart = bce->offset();
 
         /* Indicate that we're emitting a subroutine body. */
         stmtInfo.type = STMT_SUBROUTINE;
-        if (!UpdateLineNumberNotes(cx, bce, pn->pn_kid3->pn_pos.begin.lineno))
+        if (!UpdateSourceCoordNotes(cx, bce, pn->pn_kid3->pn_pos.begin))
             return false;
         if (Emit1(cx, bce, JSOP_FINALLY) < 0 ||
             !EmitTree(cx, bce, pn->pn_kid3) ||
             Emit1(cx, bce, JSOP_RETSUB) < 0)
         {
             return false;
         }
         JS_ASSERT(bce->stackDepth == depth);
@@ -4694,16 +4711,18 @@ EmitNormalFor(JSContext *cx, BytecodeEmi
 #if JS_HAS_DESTRUCTURING
         if (pn3->isKind(PNK_ASSIGN)) {
             JS_ASSERT(pn3->isOp(JSOP_NOP));
             if (!MaybeEmitGroupAssignment(cx, bce, op, pn3, GroupIsNotDecl, &op))
                 return false;
         }
 #endif
         if (op == JSOP_POP) {
+            if (!UpdateSourceCoordNotes(cx, bce, pn3->pn_pos.begin))
+                return false;
             if (!EmitTree(cx, bce, pn3))
                 return false;
             if (pn3->isKind(PNK_VAR) || pn3->isKind(PNK_CONST) || pn3->isKind(PNK_LET)) {
                 /*
                  * Check whether a destructuring-initialized var decl
                  * was optimized to a group assignment.  If so, we do
                  * not need to emit a pop below, so switch to a nop,
                  * just for the decompiler.
@@ -4757,16 +4776,18 @@ EmitNormalFor(JSContext *cx, BytecodeEmi
     StmtInfoBCE *stmt = &stmtInfo;
     do {
         stmt->update = bce->offset();
     } while ((stmt = stmt->down) != NULL && stmt->type == STMT_LABEL);
 
     /* Check for update code to do before the condition (if any). */
     pn3 = forHead->pn_kid3;
     if (pn3) {
+        if (!UpdateSourceCoordNotes(cx, bce, pn3->pn_pos.begin))
+            return false;
         op = JSOP_POP;
 #if JS_HAS_DESTRUCTURING
         if (pn3->isKind(PNK_ASSIGN)) {
             JS_ASSERT(pn3->isOp(JSOP_NOP));
             if (!MaybeEmitGroupAssignment(cx, bce, op, pn3, GroupIsNotDecl, &op))
                 return false;
         }
 #endif
@@ -4905,17 +4926,17 @@ EmitFunc(JSContext *cx, BytecodeEmitter 
      */
     if (!bce->sc->inFunction()) {
         JS_ASSERT(pn->pn_cookie.isFree());
         JS_ASSERT(pn->getOp() == JSOP_NOP);
         JS_ASSERT(!bce->topStmt);
         bce->switchToProlog();
         if (!EmitIndex32(cx, JSOP_DEFFUN, index, bce))
             return false;
-        if (!UpdateLineNumberNotes(cx, bce, pn->pn_pos.begin.lineno))
+        if (!UpdateSourceCoordNotes(cx, bce, pn->pn_pos.begin))
             return false;
         bce->switchToMain();
 
         /* Emit NOP for the decompiler. */
         if (!EmitFunctionDefNop(cx, bce, index))
             return false;
     } else {
 #ifdef DEBUG
@@ -5097,16 +5118,19 @@ EmitContinue(JSContext *cx, BytecodeEmit
     }
 
     return EmitGoto(cx, bce, stmt, &stmt->continues, labelIndex, noteType) >= 0;
 }
 
 static bool
 EmitReturn(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
+    if (!UpdateSourceCoordNotes(cx, bce, pn->pn_pos.begin))
+        return false;
+
     /* Push a return value */
     if (ParseNode *pn2 = pn->pn_kid) {
         if (!EmitTree(cx, bce, pn2))
             return false;
     } else {
         /* No explicit return value provided */
         if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
             return false;
@@ -5174,16 +5198,19 @@ static bool
 EmitStatement(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
     JS_ASSERT(pn->isKind(PNK_SEMI));
 
     ParseNode *pn2 = pn->pn_kid;
     if (!pn2)
         return true;
 
+    if (!UpdateSourceCoordNotes(cx, bce, pn->pn_pos.begin))
+        return false;
+
     /*
      * Top-level or called-from-a-native JS_Execute/EvaluateScript,
      * debugger, and eval frames may need the value of the ultimate
      * expression statement as the script's result, despite the fact
      * that it appears useless to the compiler.
      *
      * API users may also set the JSOPTION_NO_SCRIPT_RVAL option when
      * calling JS_Compile* to suppress JSOP_POPV.
@@ -5922,16 +5949,18 @@ EmitArray(JSContext *cx, BytecodeEmitter
 
     /* Emit an op to finish the array and aid in decompilation. */
     return Emit1(cx, bce, JSOP_ENDINIT) >= 0;
 }
 
 static bool
 EmitUnary(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
+    if (!UpdateSourceCoordNotes(cx, bce, pn->pn_pos.begin))
+        return false;
     /* Unary op, including unary +/-. */
     JSOp op = pn->getOp();
     ParseNode *pn2 = pn->pn_kid;
 
     JS_ASSERT(op != JSOP_XMLNAME);
     if (op == JSOP_TYPEOF && !pn2->isKind(PNK_NAME))
         op = JSOP_TYPEOFEXPR;
 
@@ -6023,17 +6052,18 @@ frontend::EmitTree(JSContext *cx, Byteco
 
     EmitLevelManager elm(bce);
 
     bool ok = true;
     ptrdiff_t top = bce->offset();
     pn->pn_offset = top;
 
     /* Emit notes to tell the current bytecode's source line number. */
-    UPDATE_LINE_NUMBER_NOTES(cx, bce, pn->pn_pos.begin.lineno);
+    if (!UpdateLineNumberNotes(cx, bce, pn->pn_pos.begin.lineno))
+        return false;
 
     switch (pn->getKind()) {
       case PNK_FUNCTION:
         ok = EmitFunc(cx, bce, pn);
         break;
 
       case PNK_ARGSBODY:
       {
@@ -6622,17 +6652,17 @@ frontend::EmitTree(JSContext *cx, Byteco
         break;
 
       default:
         JS_ASSERT(0);
     }
 
     /* bce->emitLevel == 1 means we're last on the stack, so finish up. */
     if (ok && bce->emitLevel == 1) {
-        if (!UpdateLineNumberNotes(cx, bce, pn->pn_pos.end.lineno))
+        if (!UpdateSourceCoordNotes(cx, bce, pn->pn_pos.end))
             return false;
     }
 
     return ok;
 }
 
 static int
 AllocSrcNote(JSContext *cx, BytecodeEmitter *bce)
@@ -7087,17 +7117,17 @@ JS_FRIEND_DATA(JSSrcNoteSpec) js_SrcNote
     {"label",           1},
     {"labelbrace",      1},
     {"endbrace",        0},
     {"break2label",     1},
     {"cont2label",      1},
     {"switch",          2},
     {"funcdef",         1},
     {"catch",           1},
-    {"unused",         -1},
+    {"colspan",         1},
     {"newline",         0},
     {"setline",         1},
     {"xdelta",          0},
 };
 
 JS_FRIEND_API(unsigned)
 js_SrcNoteLength(jssrcnote *sn)
 {
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -68,16 +68,18 @@ struct BytecodeEmitter
         jsbytecode  *base;          /* base of JS bytecode vector */
         jsbytecode  *limit;         /* one byte beyond end of bytecode */
         jsbytecode  *next;          /* pointer to next free bytecode */
         jssrcnote   *notes;         /* source notes, see below */
         unsigned    noteCount;      /* number of source notes so far */
         unsigned    noteLimit;      /* limit number for source notes in notePool */
         ptrdiff_t   lastNoteOffset; /* code offset for last source note */
         unsigned    currentLine;    /* line number for tree-based srcnote gen */
+        unsigned    lastColumn;     /* zero-based column index on currentLine of
+                                       last SRC_COLSPAN-annotated opcode */
     } prolog, main, *current;
 
     Parser          *const parser;  /* the parser */
 
     StackFrame      *const callerFrame; /* scripted caller frame for eval and dbgapi */
 
     StmtInfoBCE     *topStmt;       /* top of statement info stack */
     StmtInfoBCE     *topScopeStmt;  /* top lexical scope statement */
@@ -169,16 +171,17 @@ struct BytecodeEmitter
     void switchToMain() { current = &main; }
     void switchToProlog() { current = &prolog; }
 
     jssrcnote *notes() const { return current->notes; }
     unsigned noteCount() const { return current->noteCount; }
     unsigned noteLimit() const { return current->noteLimit; }
     ptrdiff_t lastNoteOffset() const { return current->lastNoteOffset; }
     unsigned currentLine() const { return current->currentLine; }
+    unsigned lastColumn() const { return current->lastColumn; }
 
     inline ptrdiff_t countFinalSourceNotes();
 
     bool reportError(ParseNode *pn, unsigned errorNumber, ...);
     bool reportStrictWarning(ParseNode *pn, unsigned errorNumber, ...);
     bool reportStrictModeError(ParseNode *pn, unsigned errorNumber, ...);
 };
 
@@ -244,17 +247,17 @@ EmitFunctionScript(JSContext *cx, Byteco
  * encoded immediately after them, in note bytes or byte-triples.
  *
  *                 Source Note               Extended Delta
  *              +7-6-5-4-3+2-1-0+           +7-6-5+4-3-2-1-0+
  *              |note-type|delta|           |1 1| ext-delta |
  *              +---------+-----+           +---+-----------+
  *
  * At most one "gettable" note (i.e., a note of type other than SRC_NEWLINE,
- * SRC_SETLINE, and SRC_XDELTA) applies to a given bytecode.
+ * SRC_COLSPAN, SRC_SETLINE, and SRC_XDELTA) applies to a given bytecode.
  *
  * NB: the js_SrcNoteSpec array in BytecodeEmitter.cpp is indexed by this
  * enum, so its initializers need to match the order here.
  *
  * Note on adding new source notes: every pair of bytecodes (A, B) where A and
  * B have disjoint sets of source notes that could apply to each bytecode may
  * reuse the same note type value for two notes (snA, snB) that have the same
  * arity in JSSrcNoteSpec. This is why SRC_IF and SRC_INITPROP have the same
@@ -305,17 +308,17 @@ enum SrcNoteType {
     SRC_ENDBRACE    = 15,       /* JSOP_NOP for label: {...} end brace */
     SRC_BREAK2LABEL = 16,       /* JSOP_GOTO for 'break label' with atomid */
     SRC_CONT2LABEL  = 17,       /* JSOP_GOTO for 'continue label' with atomid */
     SRC_SWITCH      = 18,       /* JSOP_*SWITCH with offset to end of switch,
                                    2nd off to first JSOP_CASE if condswitch */
     SRC_SWITCHBREAK = 18,       /* JSOP_GOTO is a break in a switch */
     SRC_FUNCDEF     = 19,       /* JSOP_NOP for function f() with atomid */
     SRC_CATCH       = 20,       /* catch block has guard */
-                                /* 21 is unused */
+    SRC_COLSPAN     = 21,       /* number of columns this opcode spans */
     SRC_NEWLINE     = 22,       /* bytecode follows a source newline */
     SRC_SETLINE     = 23,       /* a file-absolute source line number note */
     SRC_XDELTA      = 24        /* 24-31 are for extended delta notes */
 };
 
 /*
  * Constants for the SRC_DECL source note.
  *
@@ -341,17 +344,17 @@ enum SrcNoteType {
                                           ((SRC_XDELTA << SN_DELTA_BITS)      \
                                            | ((d) & SN_XDELTA_MASK)))
 
 #define SN_IS_XDELTA(sn)        ((*(sn) >> SN_DELTA_BITS) >= SRC_XDELTA)
 #define SN_TYPE(sn)             ((js::SrcNoteType)(SN_IS_XDELTA(sn)           \
                                                    ? SRC_XDELTA               \
                                                    : *(sn) >> SN_DELTA_BITS))
 #define SN_SET_TYPE(sn,type)    SN_MAKE_NOTE(sn, type, SN_DELTA(sn))
-#define SN_IS_GETTABLE(sn)      (SN_TYPE(sn) < SRC_NEWLINE)
+#define SN_IS_GETTABLE(sn)      (SN_TYPE(sn) < SRC_COLSPAN)
 
 #define SN_DELTA(sn)            ((ptrdiff_t)(SN_IS_XDELTA(sn)                 \
                                              ? *(sn) & SN_XDELTA_MASK         \
                                              : *(sn) & SN_DELTA_MASK))
 #define SN_SET_DELTA(sn,delta)  (SN_IS_XDELTA(sn)                             \
                                  ? SN_MAKE_XDELTA(sn, delta)                  \
                                  : SN_MAKE_NOTE(sn, SN_TYPE(sn), delta))
 
@@ -361,16 +364,29 @@ enum SrcNoteType {
 /*
  * Offset fields follow certain notes and are frequency-encoded: an offset in
  * [0,0x7f] consumes one byte, an offset in [0x80,0x7fffff] takes three, and
  * the high bit of the first byte is set.
  */
 #define SN_3BYTE_OFFSET_FLAG    0x80
 #define SN_3BYTE_OFFSET_MASK    0x7f
 
+/*
+ * Negative SRC_COLSPAN offsets are rare, but can arise with for(;;) loops and
+ * other constructs that generate code in non-source order. They can also arise
+ * due to failure to update pn->pn_pos.end to be the last child's end -- such
+ * failures are bugs to fix.
+ *
+ * Source note offsets in general must be non-negative and less than 0x800000,
+ * per the above SN_3BYTE_* definitions. To encode negative colspans, we bias
+ * them by the offset domain size and restrict non-negative colspans to less
+ * than half this domain.
+ */
+#define SN_COLSPAN_DOMAIN       ptrdiff_t(SN_3BYTE_OFFSET_FLAG << 16)
+
 #define SN_MAX_OFFSET ((size_t)((ptrdiff_t)SN_3BYTE_OFFSET_FLAG << 16) - 1)
 
 #define SN_LENGTH(sn)           ((js_SrcNoteSpec[SN_TYPE(sn)].arity == 0) ? 1 \
                                  : js_SrcNoteLength(sn))
 #define SN_NEXT(sn)             ((sn) + SN_LENGTH(sn))
 
 /* A source note array is terminated by an all-zero element. */
 #define SN_MAKE_TERMINATOR(sn)  (*(sn) = SRC_NULL)
--- a/js/src/jit-test/jit_test.py
+++ b/js/src/jit-test/jit_test.py
@@ -254,17 +254,17 @@ def check_output(out, err, rc, test):
 
     for line in err.split('\n'):
         if 'Assertion failed:' in line:
             return False
 
     if rc != test.expect_status:
         # Allow a non-zero exit code if we want to allow OOM, but only if we
         # actually got OOM.
-        return test.allow_oom and ': out of memory' in err and 'Assertion failure' not in err
+        return test.allow_oom and 'out of memory' in err and 'Assertion failure' not in err
 
     return True
 
 def print_tinderbox(label, test, message=None):
     jitflags = " ".join(test.jitflags)
     result = "%s | jit_test.py %-15s| %s" % (label, jitflags, test.path)
     if message:
         result += ": " + message
--- a/js/src/jsapi-tests/Makefile.in
+++ b/js/src/jsapi-tests/Makefile.in
@@ -60,16 +60,17 @@ CPPSRCS = \
   testTrap.cpp \
   testTypedArrays.cpp \
   testUTF8.cpp \
   testValueABI.cpp \
   testVersion.cpp \
   testXDR.cpp \
   testProfileStrings.cpp \
   testJSEvaluateScript.cpp \
+  testErrorCopying.cpp \
   $(NULL)
 
 CSRCS = \
   valueABI.c
 
 # Disabled: an entirely unrelated test seems to cause this to fail.  Moreover,
 # given the test's dependence on interactions between the compiler, the GC, and
 # conservative stack scanning, the fix isn't obvious: more investigation
new file mode 100644
--- /dev/null
+++ b/js/src/jsapi-tests/testErrorCopying.cpp
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sw=4 et tw=99:
+ *
+ * Tests that the column number of error reports is properly copied over from
+ * other reports when invoked from the C++ api.
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "tests.h"
+#include "jscntxt.h"
+
+static uint32_t column = 0;
+
+static void
+my_ErrorReporter(JSContext *cx, const char *message, JSErrorReport *report)
+{
+    column = report->column;
+}
+
+BEGIN_TEST(testErrorCopying_columnCopied)
+{
+        //0         1         2
+        //0123456789012345678901234567
+    EXEC("function check() { Object; foo; }");
+
+    JS::RootedValue rval(cx);
+    JS_SetErrorReporter(cx, my_ErrorReporter);
+    CHECK(!JS_CallFunctionName(cx, global, "check", 0, NULL, rval.address()));
+    CHECK(column == 27);
+    return true;
+}
+END_TEST(testErrorCopying_columnCopied)
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -6034,16 +6034,17 @@ struct JSErrorReport {
     const char      *tokenptr;      /* pointer to error token in linebuf */
     const jschar    *uclinebuf;     /* unicode (original) line buffer */
     const jschar    *uctokenptr;    /* unicode (original) token pointer */
     unsigned           flags;          /* error/warning, etc. */
     unsigned           errorNumber;    /* the error number, e.g. see js.msg */
     const jschar    *ucmessage;     /* the (default) error message */
     const jschar    **messageArgs;  /* arguments for the error message */
     int16_t         exnType;        /* One of the JSExnType constants */
+    unsigned           column;         /* zero-based column index in line */
 };
 
 /*
  * JSErrorReport flag values.  These may be freely composed.
  */
 #define JSREPORT_ERROR      0x0     /* pseudo-flag for default case */
 #define JSREPORT_WARNING    0x1     /* reported via JS_ReportWarning */
 #define JSREPORT_EXCEPTION  0x2     /* exception was thrown */
--- a/js/src/jsatom.tbl
+++ b/js/src/jsatom.tbl
@@ -38,16 +38,17 @@ DEFINE_ATOM(anonymous, "anonymous")
 DEFINE_ATOM(apply, "apply")
 DEFINE_ATOM(arguments, "arguments")
 DEFINE_ATOM(arity, "arity")
 DEFINE_ATOM(BYTES_PER_ELEMENT, "BYTES_PER_ELEMENT")
 DEFINE_ATOM(call, "call")
 DEFINE_ATOM(callee, "callee")
 DEFINE_ATOM(caller, "caller")
 DEFINE_ATOM(classPrototype, "prototype")
+DEFINE_ATOM(columnNumber, "columnNumber")
 DEFINE_ATOM(constructor, "constructor")
 DEFINE_ATOM(each, "each")
 DEFINE_ATOM(eval, "eval")
 DEFINE_ATOM(fileName, "fileName")
 DEFINE_ATOM(get, "get")
 DEFINE_ATOM(global, "global")
 DEFINE_ATOM(ignoreCase, "ignoreCase")
 DEFINE_ATOM(index, "index")
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -469,17 +469,17 @@ PopulateReportBlame(JSContext *cx, JSErr
      * Walk stack until we find a frame that is associated with a non-builtin
      * rather than a builtin frame.
      */
     NonBuiltinScriptFrameIter iter(cx);
     if (iter.done())
         return;
 
     report->filename = iter.script()->filename;
-    report->lineno = PCToLineNumber(iter.script(), iter.pc());
+    report->lineno = PCToLineNumber(iter.script(), iter.pc(), &report->column);
     report->originPrincipals = iter.script()->originPrincipals;
 }
 
 /*
  * We don't post an exception in this case, since doing so runs into
  * complications of pre-allocating an exception object which required
  * running the Exception class initializer early etc.
  * Instead we just invoke the errorReporter with an "Out Of Memory"
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -91,16 +91,17 @@ typedef JSStackTraceElemImpl<JSString *>
 
 struct JSExnPrivate
 {
     /* A copy of the JSErrorReport originally generated. */
     JSErrorReport       *errorReport;
     js::HeapPtrString   message;
     js::HeapPtrString   filename;
     unsigned            lineno;
+    unsigned            column;
     size_t              stackDepth;
     int                 exnType;
     JSStackTraceElem    stackElems[1];
 };
 
 static JSString *
 StackTraceToString(JSContext *cx, JSExnPrivate *priv);
 
@@ -211,16 +212,17 @@ CopyErrorReport(JSContext *cx, JSErrorRe
     }
     JS_ASSERT(cursor + filenameSize == (uint8_t *)copy + mallocSize);
 
     /* HOLD called by the destination error object. */
     copy->originPrincipals = report->originPrincipals;
 
     /* Copy non-pointer members. */
     copy->lineno = report->lineno;
+    copy->column = report->column;
     copy->errorNumber = report->errorNumber;
     copy->exnType = report->exnType;
 
     /* Note that this is before it gets flagged with JSREPORT_EXCEPTION */
     copy->flags = report->flags;
 
 #undef JS_CHARS_SIZE
     return copy;
@@ -245,17 +247,18 @@ struct SuppressErrorsGuard
     }
 };
 
 static void
 SetExnPrivate(JSContext *cx, JSObject *exnObject, JSExnPrivate *priv);
 
 static bool
 InitExnPrivate(JSContext *cx, HandleObject exnObject, HandleString message,
-               HandleString filename, unsigned lineno, JSErrorReport *report, int exnType)
+               HandleString filename, unsigned lineno, unsigned column,
+               JSErrorReport *report, int exnType)
 {
     JS_ASSERT(exnObject->isError());
     JS_ASSERT(!exnObject->getPrivate());
 
     JSCheckAccessOp checkAccess = cx->runtime->securityCallbacks->checkObjectAccess;
 
     Vector<JSStackTraceStackElem> frames(cx);
     {
@@ -321,16 +324,17 @@ InitExnPrivate(JSContext *cx, HandleObje
         }
     } else {
         priv->errorReport = NULL;
     }
 
     priv->message.init(message);
     priv->filename.init(filename);
     priv->lineno = lineno;
+    priv->column = column;
     priv->stackDepth = frames.length();
     priv->exnType = exnType;
     for (size_t i = 0; i < frames.length(); ++i) {
         priv->stackElems[i].funName.init(frames[i].funName);
         priv->stackElems[i].filename = frames[i].filename;
         priv->stackElems[i].ulineno = frames[i].ulineno;
     }
 
@@ -431,17 +435,25 @@ exn_resolve(JSContext *cx, HandleObject 
             v = STRING_TO_JSVAL(priv->filename);
             attrs = JSPROP_ENUMERATE;
             goto define;
         }
 
         atom = cx->runtime->atomState.lineNumberAtom;
         if (str == atom) {
             prop = js_lineNumber_str;
-            v = INT_TO_JSVAL(priv->lineno);
+            v = UINT_TO_JSVAL(priv->lineno);
+            attrs = JSPROP_ENUMERATE;
+            goto define;
+        }
+
+        atom = cx->runtime->atomState.columnNumberAtom;
+        if (str == atom) {
+            prop = js_columnNumber_str;
+            v = UINT_TO_JSVAL(priv->column);
             attrs = JSPROP_ENUMERATE;
             goto define;
         }
 
         atom = cx->runtime->atomState.stackAtom;
         if (str == atom) {
             stack = StackTraceToString(cx, priv);
             if (!stack)
@@ -580,26 +592,26 @@ Exception(JSContext *cx, unsigned argc, 
                 filename = FilenameToString(cx, cfilename);
                 if (!filename)
                     return false;
             }
         }
     }
 
     /* Set the 'lineNumber' property. */
-    uint32_t lineno;
+    uint32_t lineno, column = 0;
     if (args.length() > 2) {
         if (!ToUint32(cx, args[2], &lineno))
             return false;
     } else {
-        lineno = iter.done() ? 0 : PCToLineNumber(iter.script(), iter.pc());
+        lineno = iter.done() ? 0 : PCToLineNumber(iter.script(), iter.pc(), &column);
     }
 
     int exnType = args.callee().toFunction()->getExtendedSlot(0).toInt32();
-    if (!InitExnPrivate(cx, obj, message, filename, lineno, NULL, exnType))
+    if (!InitExnPrivate(cx, obj, message, filename, lineno, column, NULL, exnType))
         return false;
 
     args.rval().setObject(*obj);
     return true;
 }
 
 /* ES5 15.11.4.4 (NB: with subsequent errata). */
 static JSBool
@@ -786,23 +798,26 @@ InitErrorClass(JSContext *cx, Handle<Glo
 
     RootedValue nameValue(cx, StringValue(name));
     RootedValue zeroValue(cx, Int32Value(0));
     RootedValue empty(cx, StringValue(cx->runtime->emptyString));
     RootedId nameId(cx, NameToId(cx->runtime->atomState.nameAtom));
     RootedId messageId(cx, NameToId(cx->runtime->atomState.messageAtom));
     RootedId fileNameId(cx, NameToId(cx->runtime->atomState.fileNameAtom));
     RootedId lineNumberId(cx, NameToId(cx->runtime->atomState.lineNumberAtom));
+    RootedId columnNumberId(cx, NameToId(cx->runtime->atomState.columnNumberAtom));
     if (!DefineNativeProperty(cx, errorProto, nameId, nameValue,
                               JS_PropertyStub, JS_StrictPropertyStub, 0, 0, 0) ||
         !DefineNativeProperty(cx, errorProto, messageId, empty,
                               JS_PropertyStub, JS_StrictPropertyStub, 0, 0, 0) ||
         !DefineNativeProperty(cx, errorProto, fileNameId, empty,
                               JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE, 0, 0) ||
         !DefineNativeProperty(cx, errorProto, lineNumberId, zeroValue,
+                              JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE, 0, 0) ||
+        !DefineNativeProperty(cx, errorProto, columnNumberId, zeroValue,
                               JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE, 0, 0))
     {
         return NULL;
     }
 
     /* Create the corresponding constructor. */
     RootedFunction ctor(cx, global->createConstructor(cx, Exception, name, 1,
                                                       JSFunction::ExtendedFinalizeKind));
@@ -983,17 +998,17 @@ js_ErrorToException(JSContext *cx, const
     tv[2] = STRING_TO_JSVAL(messageStr);
 
     RootedString filenameStr(cx, JS_NewStringCopyZ(cx, reportp->filename));
     if (!filenameStr)
         return false;
     tv[3] = STRING_TO_JSVAL(filenameStr);
 
     if (!InitExnPrivate(cx, errObject, messageStr, filenameStr,
-                        reportp->lineno, reportp, exn)) {
+                        reportp->lineno, reportp->column, reportp, exn)) {
         return false;
     }
 
     JS_SetPendingException(cx, OBJECT_TO_JSVAL(errObject));
 
     /* Flag the error report passed in to indicate an exception was raised. */
     reportp->flags |= JSREPORT_EXCEPTION;
     return true;
@@ -1103,21 +1118,29 @@ js_ReportUncaughtException(JSContext *cx
 
         uint32_t lineno;
         if (!JS_GetProperty(cx, exnObject, js_lineNumber_str, &roots[5]) ||
             !ToUint32(cx, roots[5], &lineno))
         {
             lineno = 0;
         }
 
+        uint32_t column;
+        if (!JS_GetProperty(cx, exnObject, js_columnNumber_str, &roots[5]) ||
+            !ToUint32(cx, roots[5], &column))
+        {
+            column = 0;
+        }
+
         reportp = &report;
         PodZero(&report);
         report.filename = filename.ptr();
         report.lineno = (unsigned) lineno;
         report.exnType = int16_t(JSEXN_NONE);
+        report.column = (unsigned) column;
         if (str) {
             if (JSFixedString *fixed = str->ensureFixed(cx))
                 report.ucmessage = fixed->chars();
         }
     }
 
     JSAutoByteString bytesStorage;
     const char *bytes = NULL;
@@ -1178,16 +1201,17 @@ js_CopyErrorObject(JSContext *cx, Handle
     if (!cx->compartment->wrap(cx, &copy->message))
         return NULL;
     JS::Anchor<JSString *> messageAnchor(copy->message);
     copy->filename.init(priv->filename);
     if (!cx->compartment->wrap(cx, &copy->filename))
         return NULL;
     JS::Anchor<JSString *> filenameAnchor(copy->filename);
     copy->lineno = priv->lineno;
+    copy->column = priv->column;
     copy->stackDepth = 0;
     copy->exnType = priv->exnType;
 
     // Create the Error object.
     JSObject *proto = scope->global().getOrCreateCustomErrorPrototype(cx, copy->exnType);
     if (!proto)
         return NULL;
     JSObject *copyobj = NewObjectWithGivenProto(cx, &ErrorClass, proto, NULL);
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -1876,52 +1876,70 @@ js_GetSrcNoteCached(JSContext *cx, JSScr
             cache->code = script->code;
         }
     }
 
     return result;
 }
 
 unsigned
-js::PCToLineNumber(unsigned startLine, jssrcnote *notes, jsbytecode *code, jsbytecode *pc)
+js::PCToLineNumber(unsigned startLine, jssrcnote *notes, jsbytecode *code, jsbytecode *pc,
+                   unsigned *columnp)
 {
     unsigned lineno = startLine;
+    unsigned column = 0;
 
     /*
      * Walk through source notes accumulating their deltas, keeping track of
      * line-number notes, until we pass the note for pc's offset within
      * script->code.
      */
     ptrdiff_t offset = 0;
     ptrdiff_t target = pc - code;
     for (jssrcnote *sn = notes; !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) {
         offset += SN_DELTA(sn);
         SrcNoteType type = (SrcNoteType) SN_TYPE(sn);
         if (type == SRC_SETLINE) {
             if (offset <= target)
                 lineno = (unsigned) js_GetSrcNoteOffset(sn, 0);
+            column = 0;
         } else if (type == SRC_NEWLINE) {
             if (offset <= target)
                 lineno++;
+            column = 0;
         }
+
         if (offset > target)
             break;
+
+        if (type == SRC_COLSPAN) {
+            ptrdiff_t colspan = js_GetSrcNoteOffset(sn, 0);
+
+            if (colspan >= SN_COLSPAN_DOMAIN / 2)
+                colspan -= SN_COLSPAN_DOMAIN;
+            JS_ASSERT(ptrdiff_t(column) + colspan >= 0);
+            column += colspan;
+        }
     }
 
+    if (columnp)
+        *columnp = column;
+
     return lineno;
 }
 
 unsigned
-js::PCToLineNumber(JSScript *script, jsbytecode *pc)
+js::PCToLineNumber(JSScript *script, jsbytecode *pc, unsigned *columnp)
 {
     /* Cope with StackFrame.pc value prior to entering js_Interpret. */
     if (!pc)
         return 0;
 
-    return PCToLineNumber(script->lineno, script->notes(), script->code, pc);
+    return PCToLineNumber(script->lineno, script->notes(), script->code, pc,
+                          columnp);
 }
 
 /* The line number limit is the same as the jssrcnote offset limit. */
 #define SN_LINE_LIMIT   (SN_3BYTE_OFFSET_FLAG << 16)
 
 jsbytecode *
 js_LineNumberToPC(JSScript *script, unsigned target)
 {
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -1175,20 +1175,21 @@ extern jsbytecode *
 js_LineNumberToPC(JSScript *script, unsigned lineno);
 
 extern JS_FRIEND_API(unsigned)
 js_GetScriptLineExtent(JSScript *script);
 
 namespace js {
 
 extern unsigned
-PCToLineNumber(JSScript *script, jsbytecode *pc);
+PCToLineNumber(JSScript *script, jsbytecode *pc, unsigned *columnp = NULL);
 
 extern unsigned
-PCToLineNumber(unsigned startLine, jssrcnote *notes, jsbytecode *code, jsbytecode *pc);
+PCToLineNumber(unsigned startLine, jssrcnote *notes, jsbytecode *code, jsbytecode *pc,
+               unsigned *columnp = NULL);
 
 extern unsigned
 CurrentLine(JSContext *cx);
 
 /*
  * This function returns the file and line number of the script currently
  * executing on cx. If there is no current script executing on cx (e.g., a
  * native called directly through JSAPI (e.g., by setTimeout)), NULL and 0 are
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -1578,31 +1578,38 @@ UpdateSwitchTableBounds(JSContext *cx, J
 static void
 SrcNotes(JSContext *cx, JSScript *script, Sprinter *sp)
 {
     Sprint(sp, "\nSource notes:\n");
     Sprint(sp, "%4s  %4s %5s %6s %-8s %s\n",
            "ofs", "line", "pc", "delta", "desc", "args");
     Sprint(sp, "---- ---- ----- ------ -------- ------\n");
     unsigned offset = 0;
+    unsigned colspan = 0;
     unsigned lineno = script->lineno;
     jssrcnote *notes = script->notes();
     unsigned switchTableEnd = 0, switchTableStart = 0;
     for (jssrcnote *sn = notes; !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) {
         unsigned delta = SN_DELTA(sn);
         offset += delta;
         SrcNoteType type = (SrcNoteType) SN_TYPE(sn);
         const char *name = js_SrcNoteSpec[type].name;
         if (type == SRC_LABEL) {
             /* Check if the source note is for a switch case. */
             if (switchTableStart <= offset && offset < switchTableEnd)
                 name = "case";
         }
         Sprint(sp, "%3u: %4u %5u [%4u] %-8s", unsigned(sn - notes), lineno, offset, delta, name);
         switch (type) {
+          case SRC_COLSPAN:
+            colspan = js_GetSrcNoteOffset(sn, 0);
+            if (colspan >= SN_COLSPAN_DOMAIN / 2)
+                colspan -= SN_COLSPAN_DOMAIN;
+            Sprint(sp, "%d", colspan);
+            break;
           case SRC_SETLINE:
             lineno = js_GetSrcNoteOffset(sn, 0);
             Sprint(sp, " lineno %u", lineno);
             break;
           case SRC_NEWLINE:
             ++lineno;
             break;
           case SRC_FOR:
@@ -4285,17 +4292,17 @@ my_ErrorReporter(JSContext *cx, const ch
 
     gGotError = true;
 
     prefix = NULL;
     if (report->filename)
         prefix = JS_smprintf("%s:", report->filename);
     if (report->lineno) {
         tmp = prefix;
-        prefix = JS_smprintf("%s%u: ", tmp ? tmp : "", report->lineno);
+        prefix = JS_smprintf("%s%u:%u ", tmp ? tmp : "", report->lineno, report->column);
         JS_free(cx, tmp);
     }
     if (JSREPORT_IS_WARNING(report->flags)) {
         tmp = prefix;
         prefix = JS_smprintf("%s%swarning: ",
                              tmp ? tmp : "",
                              JSREPORT_IS_STRICT(report->flags) ? "strict " : "");
         JS_free(cx, tmp);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma/extensions/errorcolumnblame.js
@@ -0,0 +1,73 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+var BUGNUMBER = 568142;
+var summary = 'error reporting blames column as well as line';
+
+function test(f, col) {
+    var caught = false;
+    try {
+        f();
+    } catch (e) {
+        caught = true;
+        assertEq(e.columnNumber, col);
+    }
+    assertEq(caught, true);
+}
+
+/* Note single hard tab before return! */
+function foo(o) {
+	return o.p;
+}
+test(foo, 1);
+
+//234567890123456789
+test(function(f) { return f.bar; }, 19);
+test(function(f) { return f(); }, 19);
+/* Cover negative colspan case using for(;;) loop with error in update part. */
+test(function(){
+        //0         1         2         3         4
+        //012345678901234567890123456789012345678901
+    eval("function baz() { for (var i = 0; i < 10; i += a.b); assertEq(i !== i, true); }");
+    baz();
+}, 41);
+
+//        1         2         3
+//234567890123456789012345678901234
+test(function() { var tmp = null; tmp(); }, 34)
+test(function() { var tmp = null;  tmp.foo; }, 35)
+
+/* Just a generic 'throw'. */
+test(function() {
+//234567890123
+    foo({}); throw new Error('a');
+}, 13);
+
+/* Be sure to report the right statement */
+test(function() {
+    function f() { return true; }
+    function g() { return false; }
+//234567890123456789012345678
+    f(); g(); f(); if (f()) a += e;
+}, 28);
+
+//2345678901234567890
+test(function() { e++; }, 18);
+test(function() {print += e; }, 17);
+test(function(){e += 1 }, 16);
+test(function() {  print[e]; }, 19);
+test(function() { e[1]; }, 18);
+test(function() { e(); }, 18);
+test(function() { 1(); }, 18);
+test(function() { Object.defineProperty() }, 18);
+
+test(function() {
+//23456789012345678901
+    function foo() { asdf; } foo()
+}, 21);
+
+reportCompare(0, 0, "ok");
+
+printStatus("All tests passed!");