Bug 889492 - Debugger does not stop at the debugger; statement in original files; r=anton
authorNick Fitzgerald <fitzgen@gmail.com>
Thu, 22 Aug 2013 10:41:48 -0700
changeset 143965 c25190a9c9ae
parent 143869 cb165f8a2f8b
child 143966 120af945491b
push id25145
push userttaubert@mozilla.com
push date2013-08-23 04:54 +0000
treeherdermozilla-central@fb2318875cd4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersanton
bugs889492
milestone26.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 889492 - Debugger does not stop at the debugger; statement in original files; r=anton
toolkit/devtools/sourcemap/SourceMap.jsm
toolkit/devtools/sourcemap/tests/unit/Utils.jsm
toolkit/devtools/sourcemap/tests/unit/test_array_set.js
toolkit/devtools/sourcemap/tests/unit/test_source_map_consumer.js
toolkit/devtools/sourcemap/tests/unit/test_source_map_generator.js
toolkit/devtools/sourcemap/tests/unit/test_source_node.js
--- a/toolkit/devtools/sourcemap/SourceMap.jsm
+++ b/toolkit/devtools/sourcemap/SourceMap.jsm
@@ -68,24 +68,28 @@ define('source-map/source-map-consumer',
     }
 
     var version = util.getArg(sourceMap, 'version');
     var sources = util.getArg(sourceMap, 'sources');
     var names = util.getArg(sourceMap, 'names');
     var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null);
     var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null);
     var mappings = util.getArg(sourceMap, 'mappings');
-    var file = util.getArg(sourceMap, 'file');
+    var file = util.getArg(sourceMap, 'file', null);
 
     if (version !== this._version) {
       throw new Error('Unsupported version: ' + version);
     }
 
-    this._names = ArraySet.fromArray(names);
-    this._sources = ArraySet.fromArray(sources);
+    // Pass `true` below to allow duplicate names and sources. While source maps
+    // are intended to be compressed and deduplicated, the TypeScript compiler
+    // sometimes generates source maps with duplicates in them. See Github issue
+    // #72 and bugzil.la/889492.
+    this._names = ArraySet.fromArray(names, true);
+    this._sources = ArraySet.fromArray(sources, true);
     this.sourceRoot = sourceRoot;
     this.sourcesContent = sourcesContent;
     this.file = file;
 
     // `this._generatedMappings` and `this._originalMappings` hold the parsed
     // mapping coordinates from the source map's "mappings" attribute. Each
     // object in the array is of the form
     //
@@ -667,37 +671,38 @@ define('source-map/array-set', ['require
   function ArraySet() {
     this._array = [];
     this._set = {};
   }
 
   /**
    * Static method for creating ArraySet instances from an existing array.
    */
-  ArraySet.fromArray = function ArraySet_fromArray(aArray) {
+  ArraySet.fromArray = function ArraySet_fromArray(aArray, aAllowDuplicates) {
     var set = new ArraySet();
     for (var i = 0, len = aArray.length; i < len; i++) {
-      set.add(aArray[i]);
+      set.add(aArray[i], aAllowDuplicates);
     }
     return set;
   };
 
   /**
    * Add the given string to this set.
    *
    * @param String aStr
    */
-  ArraySet.prototype.add = function ArraySet_add(aStr) {
-    if (this.has(aStr)) {
-      // Already a member; nothing to do.
-      return;
+  ArraySet.prototype.add = function ArraySet_add(aStr, aAllowDuplicates) {
+    var isDuplicate = this.has(aStr);
+    var idx = this._array.length;
+    if (!isDuplicate || aAllowDuplicates) {
+      this._array.push(aStr);
     }
-    var idx = this._array.length;
-    this._array.push(aStr);
-    this._set[util.toSetString(aStr)] = idx;
+    if (!isDuplicate) {
+      this._set[util.toSetString(aStr)] = idx;
+    }
   };
 
   /**
    * Is the given string a member of this set?
    *
    * @param String aStr
    */
   ArraySet.prototype.has = function ArraySet_has(aStr) {
@@ -1418,17 +1423,17 @@ define('source-map/source-node', ['requi
         if (content) {
           node.setSourceContent(sourceFile, content);
         }
       });
 
       return node;
 
       function addMappingWithCode(mapping, code) {
-        if (mapping.source === undefined) {
+        if (mapping === null || mapping.source === undefined) {
           node.add(code);
         } else {
           node.add(new SourceNode(mapping.originalLine,
                                   mapping.originalColumn,
                                   mapping.source,
                                   code,
                                   mapping.name));
         }
@@ -1598,41 +1603,55 @@ define('source-map/source-node', ['requi
   SourceNode.prototype.toStringWithSourceMap = function SourceNode_toStringWithSourceMap(aArgs) {
     var generated = {
       code: "",
       line: 1,
       column: 0
     };
     var map = new SourceMapGenerator(aArgs);
     var sourceMappingActive = false;
+    var lastOriginalSource = null;
+    var lastOriginalLine = null;
+    var lastOriginalColumn = null;
+    var lastOriginalName = null;
     this.walk(function (chunk, original) {
       generated.code += chunk;
       if (original.source !== null
           && original.line !== null
           && original.column !== null) {
-        map.addMapping({
-          source: original.source,
-          original: {
-            line: original.line,
-            column: original.column
-          },
-          generated: {
-            line: generated.line,
-            column: generated.column
-          },
-          name: original.name
-        });
+        if(lastOriginalSource !== original.source
+           || lastOriginalLine !== original.line
+           || lastOriginalColumn !== original.column
+           || lastOriginalName !== original.name) {
+          map.addMapping({
+            source: original.source,
+            original: {
+              line: original.line,
+              column: original.column
+            },
+            generated: {
+              line: generated.line,
+              column: generated.column
+            },
+            name: original.name
+          });
+        }
+        lastOriginalSource = original.source;
+        lastOriginalLine = original.line;
+        lastOriginalColumn = original.column;
+        lastOriginalName = original.name;
         sourceMappingActive = true;
       } else if (sourceMappingActive) {
         map.addMapping({
           generated: {
             line: generated.line,
             column: generated.column
           }
         });
+        lastOriginalSource = null;
         sourceMappingActive = false;
       }
       chunk.split('').forEach(function (ch) {
         if (ch === '\n') {
           generated.line++;
           generated.column = 0;
         } else {
           generated.column++;
--- a/toolkit/devtools/sourcemap/tests/unit/Utils.jsm
+++ b/toolkit/devtools/sourcemap/tests/unit/Utils.jsm
@@ -122,16 +122,24 @@ define('test/source-map/util', ['require
       ' };',
       ' TWO.inc = function (n) {\n' +
       '   return n + 1;\n' +
       ' };'
     ],
     sourceRoot: '/the/root',
     mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
   };
+  exports.emptyMap = {
+    version: 3,
+    file: 'min.js',
+    names: [],
+    sources: [],
+    mappings: ''
+  };
+
 
   function assertMapping(generatedLine, generatedColumn, originalSource,
                          originalLine, originalColumn, name, map, assert,
                          dontTestGenerated, dontTestOriginal) {
     if (!dontTestOriginal) {
       var origMapping = map.originalPositionFor({
         line: generatedLine,
         column: generatedColumn
--- a/toolkit/devtools/sourcemap/tests/unit/test_array_set.js
+++ b/toolkit/devtools/sourcemap/tests/unit/test_array_set.js
@@ -68,12 +68,45 @@ define("test/source-map/test-array-set",
   exports['test that you can add __proto__; see github issue #30'] = function (assert, util) {
     var set = new ArraySet();
     set.add('__proto__');
     assert.ok(set.has('__proto__'));
     assert.strictEqual(set.at(0), '__proto__');
     assert.strictEqual(set.indexOf('__proto__'), 0);
   };
 
+  exports['test .fromArray() with duplicates'] = function (assert, util) {
+    var set = ArraySet.fromArray(['foo', 'foo']);
+    assert.ok(set.has('foo'));
+    assert.strictEqual(set.at(0), 'foo');
+    assert.strictEqual(set.indexOf('foo'), 0);
+    assert.strictEqual(set.toArray().length, 1);
+
+    set = ArraySet.fromArray(['foo', 'foo'], true);
+    assert.ok(set.has('foo'));
+    assert.strictEqual(set.at(0), 'foo');
+    assert.strictEqual(set.at(1), 'foo');
+    assert.strictEqual(set.indexOf('foo'), 0);
+    assert.strictEqual(set.toArray().length, 2);
+  };
+
+  exports['test .add() with duplicates'] = function (assert, util) {
+    var set = new ArraySet();
+    set.add('foo');
+
+    set.add('foo');
+    assert.ok(set.has('foo'));
+    assert.strictEqual(set.at(0), 'foo');
+    assert.strictEqual(set.indexOf('foo'), 0);
+    assert.strictEqual(set.toArray().length, 1);
+
+    set.add('foo', true);
+    assert.ok(set.has('foo'));
+    assert.strictEqual(set.at(0), 'foo');
+    assert.strictEqual(set.at(1), 'foo');
+    assert.strictEqual(set.indexOf('foo'), 0);
+    assert.strictEqual(set.toArray().length, 2);
+  };
+
 });
 function run_test() {
   runSourceMapTests('test/source-map/test-array-set', do_throw);
 }
--- a/toolkit/devtools/sourcemap/tests/unit/test_source_map_consumer.js
+++ b/toolkit/devtools/sourcemap/tests/unit/test_source_map_consumer.js
@@ -318,12 +318,82 @@ define("test/source-map/test-source-map-
       "mappings": "AACA",
       "sourcesContent": ["foo"]
     });
 
     var s = map.sources[0];
     assert.equal(map.sourceContentFor(s), "foo");
   };
 
+  exports['test github issue #72, duplicate sources'] = function (assert, util) {
+    var map = new SourceMapConsumer({
+      "version": 3,
+      "file": "foo.js",
+      "sources": ["source1.js", "source1.js", "source3.js"],
+      "names": [],
+      "mappings": ";EAAC;;IAEE;;MEEE",
+      "sourceRoot": "http://example.com"
+    });
+
+    var pos = map.originalPositionFor({
+      line: 2,
+      column: 2
+    });
+    assert.equal(pos.source, 'http://example.com/source1.js');
+    assert.equal(pos.line, 1);
+    assert.equal(pos.column, 1);
+
+    var pos = map.originalPositionFor({
+      line: 4,
+      column: 4
+    });
+    assert.equal(pos.source, 'http://example.com/source1.js');
+    assert.equal(pos.line, 3);
+    assert.equal(pos.column, 3);
+
+    var pos = map.originalPositionFor({
+      line: 6,
+      column: 6
+    });
+    assert.equal(pos.source, 'http://example.com/source3.js');
+    assert.equal(pos.line, 5);
+    assert.equal(pos.column, 5);
+  };
+
+  exports['test github issue #72, duplicate names'] = function (assert, util) {
+    var map = new SourceMapConsumer({
+      "version": 3,
+      "file": "foo.js",
+      "sources": ["source.js"],
+      "names": ["name1", "name1", "name3"],
+      "mappings": ";EAACA;;IAEEA;;MAEEE",
+      "sourceRoot": "http://example.com"
+    });
+
+    var pos = map.originalPositionFor({
+      line: 2,
+      column: 2
+    });
+    assert.equal(pos.name, 'name1');
+    assert.equal(pos.line, 1);
+    assert.equal(pos.column, 1);
+
+    var pos = map.originalPositionFor({
+      line: 4,
+      column: 4
+    });
+    assert.equal(pos.name, 'name1');
+    assert.equal(pos.line, 3);
+    assert.equal(pos.column, 3);
+
+    var pos = map.originalPositionFor({
+      line: 6,
+      column: 6
+    });
+    assert.equal(pos.name, 'name3');
+    assert.equal(pos.line, 5);
+    assert.equal(pos.column, 5);
+  };
+
 });
 function run_test() {
   runSourceMapTests('test/source-map/test-source-map-consumer', do_throw);
 }
--- a/toolkit/devtools/sourcemap/tests/unit/test_source_map_generator.js
+++ b/toolkit/devtools/sourcemap/tests/unit/test_source_map_generator.js
@@ -388,12 +388,38 @@ define("test/source-map/test-source-map-
 
     map1.addMapping(fullMapping2);
     map1.addMapping(fullMapping1);
 
     map2.addMapping(fullMapping2);
 
     util.assertEqualMaps(assert, map1.toJSON(), map2.toJSON());
   };
+
+  exports['test github issue #72, check for duplicate names or sources'] = function (assert, util) {
+    var map = new SourceMapGenerator({
+      file: 'test.js'
+    });
+    map.addMapping({
+      generated: { line: 1, column: 1 },
+      original: { line: 2, column: 2 },
+      source: 'a.js',
+      name: 'foo'
+    });
+    map.addMapping({
+      generated: { line: 3, column: 3 },
+      original: { line: 4, column: 4 },
+      source: 'a.js',
+      name: 'foo'
+    });
+    util.assertEqualMaps(assert, map.toJSON(), {
+      version: 3,
+      file: 'test.js',
+      sources: ['a.js'],
+      names: ['foo'],
+      mappings: 'CACEA;;GAEEA'
+    });
+  };
+
 });
 function run_test() {
   runSourceMapTests('test/source-map/test-source-map-generator', do_throw);
 }
--- a/toolkit/devtools/sourcemap/tests/unit/test_source_node.js
+++ b/toolkit/devtools/sourcemap/tests/unit/test_source_node.js
@@ -207,16 +207,35 @@ define("test/source-map/test-source-node
     assert.equal(code, util.testGeneratedCode);
     assert.ok(map instanceof SourceMapGenerator, 'map instanceof SourceMapGenerator');
     map = map.toJSON();
     assert.equal(map.version, util.testMap.version);
     assert.equal(map.file, util.testMap.file);
     assert.equal(map.mappings, util.testMap.mappings);
   };
 
+  exports['test .fromStringWithSourceMap() empty map'] = function (assert, util) {
+    var node = SourceNode.fromStringWithSourceMap(
+                              util.testGeneratedCode,
+                              new SourceMapConsumer(util.emptyMap));
+    var result = node.toStringWithSourceMap({
+      file: 'min.js'
+    });
+    var map = result.map;
+    var code = result.code;
+
+    assert.equal(code, util.testGeneratedCode);
+    assert.ok(map instanceof SourceMapGenerator, 'map instanceof SourceMapGenerator');
+    map = map.toJSON();
+    assert.equal(map.version, util.emptyMap.version);
+    assert.equal(map.file, util.emptyMap.file);
+    assert.equal(map.mappings.length, util.emptyMap.mappings.length);
+    assert.equal(map.mappings, util.emptyMap.mappings);
+  };
+
   exports['test .fromStringWithSourceMap() complex version'] = function (assert, util) {
     var input = new SourceNode(null, null, null, [
       "(function() {\n",
         "  var Test = {};\n",
         "  ", new SourceNode(1, 0, "a.js", "Test.A = { value: 1234 };\n"),
         "  ", new SourceNode(2, 0, "a.js", "Test.A.x = 'xyz';"), "\n",
         "}());\n",
         "/* Generated Source */"]);
@@ -236,16 +255,74 @@ define("test/source-map/test-source-node
 
     assert.equal(code, input.code);
     assert.ok(map instanceof SourceMapGenerator, 'map instanceof SourceMapGenerator');
     map = map.toJSON();
     var inputMap = input.map.toJSON();
     util.assertEqualMaps(assert, map, inputMap);
   };
 
+  exports['test .fromStringWithSourceMap() merging duplicate mappings'] = function (assert, util) {
+    var input = new SourceNode(null, null, null, [
+      new SourceNode(1, 0, "a.js", "(function"), new SourceNode(1, 0, "a.js", "() {\n"),
+        "  ", new SourceNode(1, 0, "a.js", "var Test = "), new SourceNode(1, 0, "b.js", "{};\n"),
+        new SourceNode(2, 0, "b.js", "Test"), new SourceNode(2, 0, "b.js", ".A", "A"), new SourceNode(2, 20, "b.js", " = { value: 1234 };\n", "A"),
+        "}());\n",
+        "/* Generated Source */"]);
+    input = input.toStringWithSourceMap({
+      file: 'foo.js'
+    });
+
+    var correctMap = new SourceMapGenerator({
+      file: 'foo.js'
+    });
+    correctMap.addMapping({
+      generated: { line: 1, column: 0 },
+      source: 'a.js',
+      original: { line: 1, column: 0 }
+    });
+    correctMap.addMapping({
+      generated: { line: 2, column: 0 }
+    });
+    correctMap.addMapping({
+      generated: { line: 2, column: 2 },
+      source: 'a.js',
+      original: { line: 1, column: 0 }
+    });
+    correctMap.addMapping({
+      generated: { line: 2, column: 13 },
+      source: 'b.js',
+      original: { line: 1, column: 0 }
+    });
+    correctMap.addMapping({
+      generated: { line: 3, column: 0 },
+      source: 'b.js',
+      original: { line: 2, column: 0 }
+    });
+    correctMap.addMapping({
+      generated: { line: 3, column: 4 },
+      source: 'b.js',
+      name: 'A',
+      original: { line: 2, column: 0 }
+    });
+    correctMap.addMapping({
+      generated: { line: 3, column: 6 },
+      source: 'b.js',
+      name: 'A',
+      original: { line: 2, column: 20 }
+    });
+    correctMap.addMapping({
+      generated: { line: 4, column: 0 }
+    });
+
+    var inputMap = input.map.toJSON();
+    correctMap = correctMap.toJSON();
+    util.assertEqualMaps(assert, correctMap, inputMap);
+  };
+
   exports['test setSourceContent with toStringWithSourceMap'] = function (assert, util) {
     var aNode = new SourceNode(1, 1, 'a.js', 'a');
     aNode.setSourceContent('a.js', 'someContent');
     var node = new SourceNode(null, null, null,
                               ['(function () {\n',
                                '  ', aNode,
                                '  ', new SourceNode(1, 1, 'b.js', 'b'),
                                '}());']);