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 143841 c25190a9c9ae
parent 143840 cb165f8a2f8b
child 143842 120af945491b
push id2319
push usernfitzgerald@mozilla.com
push dateThu, 22 Aug 2013 17:42:07 +0000
treeherderfx-team@c25190a9c9ae [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersanton
bugs889492
milestone26.0a1
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'),
                                '}());']);