source-map-consumer.js 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236
  1. /* -*- Mode: js; js-indent-level: 2; -*- */
  2. /*
  3. * Copyright 2011 Mozilla Foundation and contributors
  4. * Licensed under the New BSD license. See LICENSE or:
  5. * http://opensource.org/licenses/BSD-3-Clause
  6. */
  7. var util = require('./util');
  8. var binarySearch = require('./binary-search');
  9. var ArraySet = require('./array-set').ArraySet;
  10. var base64VLQ = require('./base64-vlq');
  11. var quickSort = require('./quick-sort').quickSort;
  12. // Upper bound for a section's `offset.line` in an indexed source map. A flat
  13. // map spends one ';' per generated line, so a tiny indexed map with a huge
  14. // offset.line turns into a huge amount of work and memory once its mappings
  15. // are copied into a SourceMapGenerator (CVE-2026-93749). Ten million lines is
  16. // far beyond any real generated file. Columns are VLQ-encoded as deltas and
  17. // don't amplify, so they only need to be valid integers.
  18. var MAX_SECTION_OFFSET_LINE = 1e7;
  19. function SourceMapConsumer(aSourceMap, aSourceMapURL) {
  20. var sourceMap = aSourceMap;
  21. if (typeof aSourceMap === 'string') {
  22. sourceMap = util.parseSourceMapInput(aSourceMap);
  23. }
  24. return sourceMap.sections != null
  25. ? new IndexedSourceMapConsumer(sourceMap, aSourceMapURL)
  26. : new BasicSourceMapConsumer(sourceMap, aSourceMapURL);
  27. }
  28. SourceMapConsumer.fromSourceMap = function(aSourceMap, aSourceMapURL) {
  29. return BasicSourceMapConsumer.fromSourceMap(aSourceMap, aSourceMapURL);
  30. }
  31. /**
  32. * The version of the source mapping spec that we are consuming.
  33. */
  34. SourceMapConsumer.prototype._version = 3;
  35. // `__generatedMappings` and `__originalMappings` are arrays that hold the
  36. // parsed mapping coordinates from the source map's "mappings" attribute. They
  37. // are lazily instantiated, accessed via the `_generatedMappings` and
  38. // `_originalMappings` getters respectively, and we only parse the mappings
  39. // and create these arrays once queried for a source location. We jump through
  40. // these hoops because there can be many thousands of mappings, and parsing
  41. // them is expensive, so we only want to do it if we must.
  42. //
  43. // Each object in the arrays is of the form:
  44. //
  45. // {
  46. // generatedLine: The line number in the generated code,
  47. // generatedColumn: The column number in the generated code,
  48. // source: The path to the original source file that generated this
  49. // chunk of code,
  50. // originalLine: The line number in the original source that
  51. // corresponds to this chunk of generated code,
  52. // originalColumn: The column number in the original source that
  53. // corresponds to this chunk of generated code,
  54. // name: The name of the original symbol which generated this chunk of
  55. // code.
  56. // }
  57. //
  58. // All properties except for `generatedLine` and `generatedColumn` can be
  59. // `null`.
  60. //
  61. // `_generatedMappings` is ordered by the generated positions.
  62. //
  63. // `_originalMappings` is ordered by the original positions.
  64. SourceMapConsumer.prototype.__generatedMappings = null;
  65. Object.defineProperty(SourceMapConsumer.prototype, '_generatedMappings', {
  66. configurable: true,
  67. enumerable: true,
  68. get: function () {
  69. if (!this.__generatedMappings) {
  70. this._parseMappings(this._mappings, this.sourceRoot);
  71. }
  72. return this.__generatedMappings;
  73. }
  74. });
  75. SourceMapConsumer.prototype.__originalMappings = null;
  76. Object.defineProperty(SourceMapConsumer.prototype, '_originalMappings', {
  77. configurable: true,
  78. enumerable: true,
  79. get: function () {
  80. if (!this.__originalMappings) {
  81. this._parseMappings(this._mappings, this.sourceRoot);
  82. }
  83. return this.__originalMappings;
  84. }
  85. });
  86. SourceMapConsumer.prototype._charIsMappingSeparator =
  87. function SourceMapConsumer_charIsMappingSeparator(aStr, index) {
  88. var c = aStr.charAt(index);
  89. return c === ";" || c === ",";
  90. };
  91. /**
  92. * Parse the mappings in a string in to a data structure which we can easily
  93. * query (the ordered arrays in the `this.__generatedMappings` and
  94. * `this.__originalMappings` properties).
  95. */
  96. SourceMapConsumer.prototype._parseMappings =
  97. function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
  98. throw new Error("Subclasses must implement _parseMappings");
  99. };
  100. SourceMapConsumer.GENERATED_ORDER = 1;
  101. SourceMapConsumer.ORIGINAL_ORDER = 2;
  102. SourceMapConsumer.GREATEST_LOWER_BOUND = 1;
  103. SourceMapConsumer.LEAST_UPPER_BOUND = 2;
  104. /**
  105. * Iterate over each mapping between an original source/line/column and a
  106. * generated line/column in this source map.
  107. *
  108. * @param Function aCallback
  109. * The function that is called with each mapping.
  110. * @param Object aContext
  111. * Optional. If specified, this object will be the value of `this` every
  112. * time that `aCallback` is called.
  113. * @param aOrder
  114. * Either `SourceMapConsumer.GENERATED_ORDER` or
  115. * `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to
  116. * iterate over the mappings sorted by the generated file's line/column
  117. * order or the original's source/line/column order, respectively. Defaults to
  118. * `SourceMapConsumer.GENERATED_ORDER`.
  119. */
  120. SourceMapConsumer.prototype.eachMapping =
  121. function SourceMapConsumer_eachMapping(aCallback, aContext, aOrder) {
  122. var context = aContext || null;
  123. var order = aOrder || SourceMapConsumer.GENERATED_ORDER;
  124. var mappings;
  125. switch (order) {
  126. case SourceMapConsumer.GENERATED_ORDER:
  127. mappings = this._generatedMappings;
  128. break;
  129. case SourceMapConsumer.ORIGINAL_ORDER:
  130. mappings = this._originalMappings;
  131. break;
  132. default:
  133. throw new Error("Unknown order of iteration.");
  134. }
  135. var sourceRoot = this.sourceRoot;
  136. var boundCallback = aCallback.bind(context);
  137. var names = this._names;
  138. var sources = this._sources;
  139. var sourceMapURL = this._sourceMapURL;
  140. for (var i = 0, n = mappings.length; i < n; i++) {
  141. var mapping = mappings[i];
  142. var source = mapping.source === null ? null : sources.at(mapping.source);
  143. if(source !== null) {
  144. source = util.computeSourceURL(sourceRoot, source, sourceMapURL);
  145. }
  146. boundCallback({
  147. source: source,
  148. generatedLine: mapping.generatedLine,
  149. generatedColumn: mapping.generatedColumn,
  150. originalLine: mapping.originalLine,
  151. originalColumn: mapping.originalColumn,
  152. name: mapping.name === null ? null : names.at(mapping.name)
  153. });
  154. }
  155. };
  156. /**
  157. * Returns all generated line and column information for the original source,
  158. * line, and column provided. If no column is provided, returns all mappings
  159. * corresponding to a either the line we are searching for or the next
  160. * closest line that has any mappings. Otherwise, returns all mappings
  161. * corresponding to the given line and either the column we are searching for
  162. * or the next closest column that has any offsets.
  163. *
  164. * The only argument is an object with the following properties:
  165. *
  166. * - source: The filename of the original source.
  167. * - line: The line number in the original source. The line number is 1-based.
  168. * - column: Optional. the column number in the original source.
  169. * The column number is 0-based.
  170. *
  171. * and an array of objects is returned, each with the following properties:
  172. *
  173. * - line: The line number in the generated source, or null. The
  174. * line number is 1-based.
  175. * - column: The column number in the generated source, or null.
  176. * The column number is 0-based.
  177. */
  178. SourceMapConsumer.prototype.allGeneratedPositionsFor =
  179. function SourceMapConsumer_allGeneratedPositionsFor(aArgs) {
  180. var line = util.getArg(aArgs, 'line');
  181. // When there is no exact match, BasicSourceMapConsumer.prototype._findMapping
  182. // returns the index of the closest mapping less than the needle. By
  183. // setting needle.originalColumn to 0, we thus find the last mapping for
  184. // the given line, provided such a mapping exists.
  185. var needle = {
  186. source: util.getArg(aArgs, 'source'),
  187. originalLine: line,
  188. originalColumn: util.getArg(aArgs, 'column', 0)
  189. };
  190. needle.source = this._findSourceIndex(needle.source);
  191. if (needle.source < 0) {
  192. return [];
  193. }
  194. var mappings = [];
  195. var index = this._findMapping(needle,
  196. this._originalMappings,
  197. "originalLine",
  198. "originalColumn",
  199. util.compareByOriginalPositions,
  200. binarySearch.LEAST_UPPER_BOUND);
  201. if (index >= 0) {
  202. var mapping = this._originalMappings[index];
  203. if (aArgs.column === undefined) {
  204. var originalLine = mapping.originalLine;
  205. // Iterate until either we run out of mappings, or we run into
  206. // a mapping for a different line than the one we found. Since
  207. // mappings are sorted, this is guaranteed to find all mappings for
  208. // the line we found.
  209. while (mapping && mapping.originalLine === originalLine) {
  210. mappings.push({
  211. line: util.getArg(mapping, 'generatedLine', null),
  212. column: util.getArg(mapping, 'generatedColumn', null),
  213. lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
  214. });
  215. mapping = this._originalMappings[++index];
  216. }
  217. } else {
  218. var originalColumn = mapping.originalColumn;
  219. // Iterate until either we run out of mappings, or we run into
  220. // a mapping for a different line than the one we were searching for.
  221. // Since mappings are sorted, this is guaranteed to find all mappings for
  222. // the line we are searching for.
  223. while (mapping &&
  224. mapping.originalLine === line &&
  225. mapping.originalColumn == originalColumn) {
  226. mappings.push({
  227. line: util.getArg(mapping, 'generatedLine', null),
  228. column: util.getArg(mapping, 'generatedColumn', null),
  229. lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
  230. });
  231. mapping = this._originalMappings[++index];
  232. }
  233. }
  234. }
  235. return mappings;
  236. };
  237. exports.SourceMapConsumer = SourceMapConsumer;
  238. /**
  239. * A BasicSourceMapConsumer instance represents a parsed source map which we can
  240. * query for information about the original file positions by giving it a file
  241. * position in the generated source.
  242. *
  243. * The first parameter is the raw source map (either as a JSON string, or
  244. * already parsed to an object). According to the spec, source maps have the
  245. * following attributes:
  246. *
  247. * - version: Which version of the source map spec this map is following.
  248. * - sources: An array of URLs to the original source files.
  249. * - names: An array of identifiers which can be referrenced by individual mappings.
  250. * - sourceRoot: Optional. The URL root from which all sources are relative.
  251. * - sourcesContent: Optional. An array of contents of the original source files.
  252. * - mappings: A string of base64 VLQs which contain the actual mappings.
  253. * - file: Optional. The generated file this source map is associated with.
  254. *
  255. * Here is an example source map, taken from the source map spec[0]:
  256. *
  257. * {
  258. * version : 3,
  259. * file: "out.js",
  260. * sourceRoot : "",
  261. * sources: ["foo.js", "bar.js"],
  262. * names: ["src", "maps", "are", "fun"],
  263. * mappings: "AA,AB;;ABCDE;"
  264. * }
  265. *
  266. * The second parameter, if given, is a string whose value is the URL
  267. * at which the source map was found. This URL is used to compute the
  268. * sources array.
  269. *
  270. * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1#
  271. */
  272. function BasicSourceMapConsumer(aSourceMap, aSourceMapURL) {
  273. var sourceMap = aSourceMap;
  274. if (typeof aSourceMap === 'string') {
  275. sourceMap = util.parseSourceMapInput(aSourceMap);
  276. }
  277. var version = util.getArg(sourceMap, 'version');
  278. var sources = util.getArg(sourceMap, 'sources');
  279. // Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which
  280. // requires the array) to play nice here.
  281. var names = util.getArg(sourceMap, 'names', []);
  282. var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null);
  283. var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null);
  284. var mappings = util.getArg(sourceMap, 'mappings');
  285. var file = util.getArg(sourceMap, 'file', null);
  286. // Once again, Sass deviates from the spec and supplies the version as a
  287. // string rather than a number, so we use loose equality checking here.
  288. if (version != this._version) {
  289. throw new Error('Unsupported version: ' + version);
  290. }
  291. if (sourceRoot) {
  292. sourceRoot = util.normalize(sourceRoot);
  293. }
  294. sources = sources
  295. .map(String)
  296. // Some source maps produce relative source paths like "./foo.js" instead of
  297. // "foo.js". Normalize these first so that future comparisons will succeed.
  298. // See bugzil.la/1090768.
  299. .map(util.normalize)
  300. // Always ensure that absolute sources are internally stored relative to
  301. // the source root, if the source root is absolute. Not doing this would
  302. // be particularly problematic when the source root is a prefix of the
  303. // source (valid, but why??). See github issue #199 and bugzil.la/1188982.
  304. .map(function (source) {
  305. return sourceRoot && util.isAbsolute(sourceRoot) && util.isAbsolute(source)
  306. ? util.relative(sourceRoot, source)
  307. : source;
  308. });
  309. // Pass `true` below to allow duplicate names and sources. While source maps
  310. // are intended to be compressed and deduplicated, the TypeScript compiler
  311. // sometimes generates source maps with duplicates in them. See Github issue
  312. // #72 and bugzil.la/889492.
  313. this._names = ArraySet.fromArray(names.map(String), true);
  314. this._sources = ArraySet.fromArray(sources, true);
  315. this._absoluteSources = this._sources.toArray().map(function (s) {
  316. return util.computeSourceURL(sourceRoot, s, aSourceMapURL);
  317. });
  318. this.sourceRoot = sourceRoot;
  319. this.sourcesContent = sourcesContent;
  320. this._mappings = mappings;
  321. this._sourceMapURL = aSourceMapURL;
  322. this.file = file;
  323. }
  324. BasicSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype);
  325. BasicSourceMapConsumer.prototype.consumer = SourceMapConsumer;
  326. /**
  327. * Utility function to find the index of a source. Returns -1 if not
  328. * found.
  329. */
  330. BasicSourceMapConsumer.prototype._findSourceIndex = function(aSource) {
  331. var relativeSource = aSource;
  332. if (this.sourceRoot != null) {
  333. relativeSource = util.relative(this.sourceRoot, relativeSource);
  334. }
  335. if (this._sources.has(relativeSource)) {
  336. return this._sources.indexOf(relativeSource);
  337. }
  338. // Maybe aSource is an absolute URL as returned by |sources|. In
  339. // this case we can't simply undo the transform.
  340. var i;
  341. for (i = 0; i < this._absoluteSources.length; ++i) {
  342. if (this._absoluteSources[i] == aSource) {
  343. return i;
  344. }
  345. }
  346. return -1;
  347. };
  348. /**
  349. * Create a BasicSourceMapConsumer from a SourceMapGenerator.
  350. *
  351. * @param SourceMapGenerator aSourceMap
  352. * The source map that will be consumed.
  353. * @param String aSourceMapURL
  354. * The URL at which the source map can be found (optional)
  355. * @returns BasicSourceMapConsumer
  356. */
  357. BasicSourceMapConsumer.fromSourceMap =
  358. function SourceMapConsumer_fromSourceMap(aSourceMap, aSourceMapURL) {
  359. var smc = Object.create(BasicSourceMapConsumer.prototype);
  360. var names = smc._names = ArraySet.fromArray(aSourceMap._names.toArray(), true);
  361. var sources = smc._sources = ArraySet.fromArray(aSourceMap._sources.toArray(), true);
  362. smc.sourceRoot = aSourceMap._sourceRoot;
  363. smc.sourcesContent = aSourceMap._generateSourcesContent(smc._sources.toArray(),
  364. smc.sourceRoot);
  365. smc.file = aSourceMap._file;
  366. smc._sourceMapURL = aSourceMapURL;
  367. smc._absoluteSources = smc._sources.toArray().map(function (s) {
  368. return util.computeSourceURL(smc.sourceRoot, s, aSourceMapURL);
  369. });
  370. // Because we are modifying the entries (by converting string sources and
  371. // names to indices into the sources and names ArraySets), we have to make
  372. // a copy of the entry or else bad things happen. Shared mutable state
  373. // strikes again! See github issue #191.
  374. var generatedMappings = aSourceMap._mappings.toArray().slice();
  375. var destGeneratedMappings = smc.__generatedMappings = [];
  376. var destOriginalMappings = smc.__originalMappings = [];
  377. for (var i = 0, length = generatedMappings.length; i < length; i++) {
  378. var srcMapping = generatedMappings[i];
  379. var destMapping = new Mapping;
  380. destMapping.generatedLine = srcMapping.generatedLine;
  381. destMapping.generatedColumn = srcMapping.generatedColumn;
  382. if (srcMapping.source) {
  383. destMapping.source = sources.indexOf(srcMapping.source);
  384. destMapping.originalLine = srcMapping.originalLine;
  385. destMapping.originalColumn = srcMapping.originalColumn;
  386. if (srcMapping.name) {
  387. destMapping.name = names.indexOf(srcMapping.name);
  388. }
  389. destOriginalMappings.push(destMapping);
  390. }
  391. destGeneratedMappings.push(destMapping);
  392. }
  393. quickSort(smc.__originalMappings, util.compareByOriginalPositions);
  394. return smc;
  395. };
  396. /**
  397. * The version of the source mapping spec that we are consuming.
  398. */
  399. BasicSourceMapConsumer.prototype._version = 3;
  400. /**
  401. * The list of original sources.
  402. */
  403. Object.defineProperty(BasicSourceMapConsumer.prototype, 'sources', {
  404. get: function () {
  405. return this._absoluteSources.slice();
  406. }
  407. });
  408. /**
  409. * Provide the JIT with a nice shape / hidden class.
  410. */
  411. function Mapping() {
  412. this.generatedLine = 0;
  413. this.generatedColumn = 0;
  414. this.source = null;
  415. this.originalLine = null;
  416. this.originalColumn = null;
  417. this.name = null;
  418. }
  419. /**
  420. * Parse the mappings in a string in to a data structure which we can easily
  421. * query (the ordered arrays in the `this.__generatedMappings` and
  422. * `this.__originalMappings` properties).
  423. */
  424. const compareGenerated = util.compareByGeneratedPositionsDeflatedNoLine;
  425. function sortGenerated(array, start) {
  426. let l = array.length;
  427. let n = array.length - start;
  428. if (n <= 1) {
  429. return;
  430. } else if (n == 2) {
  431. let a = array[start];
  432. let b = array[start + 1];
  433. if (compareGenerated(a, b) > 0) {
  434. array[start] = b;
  435. array[start + 1] = a;
  436. }
  437. } else if (n < 20) {
  438. for (let i = start; i < l; i++) {
  439. for (let j = i; j > start; j--) {
  440. let a = array[j - 1];
  441. let b = array[j];
  442. if (compareGenerated(a, b) <= 0) {
  443. break;
  444. }
  445. array[j - 1] = b;
  446. array[j] = a;
  447. }
  448. }
  449. } else {
  450. quickSort(array, compareGenerated, start);
  451. }
  452. }
  453. BasicSourceMapConsumer.prototype._parseMappings =
  454. function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
  455. var generatedLine = 1;
  456. var previousGeneratedColumn = 0;
  457. var previousOriginalLine = 0;
  458. var previousOriginalColumn = 0;
  459. var previousSource = 0;
  460. var previousName = 0;
  461. var length = aStr.length;
  462. var index = 0;
  463. var cachedSegments = {};
  464. var temp = {};
  465. var originalMappings = [];
  466. var generatedMappings = [];
  467. var mapping, str, segment, end, value;
  468. let subarrayStart = 0;
  469. while (index < length) {
  470. if (aStr.charAt(index) === ';') {
  471. generatedLine++;
  472. index++;
  473. previousGeneratedColumn = 0;
  474. sortGenerated(generatedMappings, subarrayStart);
  475. subarrayStart = generatedMappings.length;
  476. }
  477. else if (aStr.charAt(index) === ',') {
  478. index++;
  479. }
  480. else {
  481. mapping = new Mapping();
  482. mapping.generatedLine = generatedLine;
  483. for (end = index; end < length; end++) {
  484. if (this._charIsMappingSeparator(aStr, end)) {
  485. break;
  486. }
  487. }
  488. str = aStr.slice(index, end);
  489. segment = [];
  490. while (index < end) {
  491. base64VLQ.decode(aStr, index, temp);
  492. value = temp.value;
  493. index = temp.rest;
  494. segment.push(value);
  495. }
  496. if (segment.length === 2) {
  497. throw new Error('Found a source, but no line and column');
  498. }
  499. if (segment.length === 3) {
  500. throw new Error('Found a source and line, but no column');
  501. }
  502. // Generated column.
  503. mapping.generatedColumn = previousGeneratedColumn + segment[0];
  504. previousGeneratedColumn = mapping.generatedColumn;
  505. if (segment.length > 1) {
  506. // Original source.
  507. mapping.source = previousSource + segment[1];
  508. previousSource += segment[1];
  509. // Original line.
  510. mapping.originalLine = previousOriginalLine + segment[2];
  511. previousOriginalLine = mapping.originalLine;
  512. // Lines are stored 0-based
  513. mapping.originalLine += 1;
  514. // Original column.
  515. mapping.originalColumn = previousOriginalColumn + segment[3];
  516. previousOriginalColumn = mapping.originalColumn;
  517. if (segment.length > 4) {
  518. // Original name.
  519. mapping.name = previousName + segment[4];
  520. previousName += segment[4];
  521. }
  522. }
  523. generatedMappings.push(mapping);
  524. if (typeof mapping.originalLine === 'number') {
  525. let currentSource = mapping.source;
  526. while (originalMappings.length <= currentSource) {
  527. originalMappings.push(null);
  528. }
  529. if (originalMappings[currentSource] === null) {
  530. originalMappings[currentSource] = [];
  531. }
  532. originalMappings[currentSource].push(mapping);
  533. }
  534. }
  535. }
  536. sortGenerated(generatedMappings, subarrayStart);
  537. this.__generatedMappings = generatedMappings;
  538. for (var i = 0; i < originalMappings.length; i++) {
  539. if (originalMappings[i] != null) {
  540. quickSort(originalMappings[i], util.compareByOriginalPositionsNoSource);
  541. }
  542. }
  543. this.__originalMappings = [].concat(...originalMappings);
  544. };
  545. /**
  546. * Find the mapping that best matches the hypothetical "needle" mapping that
  547. * we are searching for in the given "haystack" of mappings.
  548. */
  549. BasicSourceMapConsumer.prototype._findMapping =
  550. function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName,
  551. aColumnName, aComparator, aBias) {
  552. // To return the position we are searching for, we must first find the
  553. // mapping for the given position and then return the opposite position it
  554. // points to. Because the mappings are sorted, we can use binary search to
  555. // find the best mapping.
  556. if (aNeedle[aLineName] <= 0) {
  557. throw new TypeError('Line must be greater than or equal to 1, got '
  558. + aNeedle[aLineName]);
  559. }
  560. if (aNeedle[aColumnName] < 0) {
  561. throw new TypeError('Column must be greater than or equal to 0, got '
  562. + aNeedle[aColumnName]);
  563. }
  564. return binarySearch.search(aNeedle, aMappings, aComparator, aBias);
  565. };
  566. /**
  567. * Compute the last column for each generated mapping. The last column is
  568. * inclusive.
  569. */
  570. BasicSourceMapConsumer.prototype.computeColumnSpans =
  571. function SourceMapConsumer_computeColumnSpans() {
  572. for (var index = 0; index < this._generatedMappings.length; ++index) {
  573. var mapping = this._generatedMappings[index];
  574. // Mappings do not contain a field for the last generated columnt. We
  575. // can come up with an optimistic estimate, however, by assuming that
  576. // mappings are contiguous (i.e. given two consecutive mappings, the
  577. // first mapping ends where the second one starts).
  578. if (index + 1 < this._generatedMappings.length) {
  579. var nextMapping = this._generatedMappings[index + 1];
  580. if (mapping.generatedLine === nextMapping.generatedLine) {
  581. mapping.lastGeneratedColumn = nextMapping.generatedColumn - 1;
  582. continue;
  583. }
  584. }
  585. // The last mapping for each line spans the entire line.
  586. mapping.lastGeneratedColumn = Infinity;
  587. }
  588. };
  589. /**
  590. * Returns the original source, line, and column information for the generated
  591. * source's line and column positions provided. The only argument is an object
  592. * with the following properties:
  593. *
  594. * - line: The line number in the generated source. The line number
  595. * is 1-based.
  596. * - column: The column number in the generated source. The column
  597. * number is 0-based.
  598. * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
  599. * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
  600. * closest element that is smaller than or greater than the one we are
  601. * searching for, respectively, if the exact element cannot be found.
  602. * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
  603. *
  604. * and an object is returned with the following properties:
  605. *
  606. * - source: The original source file, or null.
  607. * - line: The line number in the original source, or null. The
  608. * line number is 1-based.
  609. * - column: The column number in the original source, or null. The
  610. * column number is 0-based.
  611. * - name: The original identifier, or null.
  612. */
  613. BasicSourceMapConsumer.prototype.originalPositionFor =
  614. function SourceMapConsumer_originalPositionFor(aArgs) {
  615. var needle = {
  616. generatedLine: util.getArg(aArgs, 'line'),
  617. generatedColumn: util.getArg(aArgs, 'column')
  618. };
  619. var index = this._findMapping(
  620. needle,
  621. this._generatedMappings,
  622. "generatedLine",
  623. "generatedColumn",
  624. util.compareByGeneratedPositionsDeflated,
  625. util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND)
  626. );
  627. if (index >= 0) {
  628. var mapping = this._generatedMappings[index];
  629. if (mapping.generatedLine === needle.generatedLine) {
  630. var source = util.getArg(mapping, 'source', null);
  631. if (source !== null) {
  632. source = this._sources.at(source);
  633. source = util.computeSourceURL(this.sourceRoot, source, this._sourceMapURL);
  634. }
  635. var name = util.getArg(mapping, 'name', null);
  636. if (name !== null) {
  637. name = this._names.at(name);
  638. }
  639. return {
  640. source: source,
  641. line: util.getArg(mapping, 'originalLine', null),
  642. column: util.getArg(mapping, 'originalColumn', null),
  643. name: name
  644. };
  645. }
  646. }
  647. return {
  648. source: null,
  649. line: null,
  650. column: null,
  651. name: null
  652. };
  653. };
  654. /**
  655. * Return true if we have the source content for every source in the source
  656. * map, false otherwise.
  657. */
  658. BasicSourceMapConsumer.prototype.hasContentsOfAllSources =
  659. function BasicSourceMapConsumer_hasContentsOfAllSources() {
  660. if (!this.sourcesContent) {
  661. return false;
  662. }
  663. return this.sourcesContent.length >= this._sources.size() &&
  664. !this.sourcesContent.some(function (sc) { return sc == null; });
  665. };
  666. /**
  667. * Returns the original source content. The only argument is the url of the
  668. * original source file. Returns null if no original source content is
  669. * available.
  670. */
  671. BasicSourceMapConsumer.prototype.sourceContentFor =
  672. function SourceMapConsumer_sourceContentFor(aSource, nullOnMissing) {
  673. if (!this.sourcesContent) {
  674. return null;
  675. }
  676. var index = this._findSourceIndex(aSource);
  677. if (index >= 0) {
  678. return this.sourcesContent[index];
  679. }
  680. var relativeSource = aSource;
  681. if (this.sourceRoot != null) {
  682. relativeSource = util.relative(this.sourceRoot, relativeSource);
  683. }
  684. var url;
  685. if (this.sourceRoot != null
  686. && (url = util.urlParse(this.sourceRoot))) {
  687. // XXX: file:// URIs and absolute paths lead to unexpected behavior for
  688. // many users. We can help them out when they expect file:// URIs to
  689. // behave like it would if they were running a local HTTP server. See
  690. // https://bugzilla.mozilla.org/show_bug.cgi?id=885597.
  691. var fileUriAbsPath = relativeSource.replace(/^file:\/\//, "");
  692. if (url.scheme == "file"
  693. && this._sources.has(fileUriAbsPath)) {
  694. return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)]
  695. }
  696. if ((!url.path || url.path == "/")
  697. && this._sources.has("/" + relativeSource)) {
  698. return this.sourcesContent[this._sources.indexOf("/" + relativeSource)];
  699. }
  700. }
  701. // This function is used recursively from
  702. // IndexedSourceMapConsumer.prototype.sourceContentFor. In that case, we
  703. // don't want to throw if we can't find the source - we just want to
  704. // return null, so we provide a flag to exit gracefully.
  705. if (nullOnMissing) {
  706. return null;
  707. }
  708. else {
  709. throw new Error('"' + relativeSource + '" is not in the SourceMap.');
  710. }
  711. };
  712. /**
  713. * Returns the generated line and column information for the original source,
  714. * line, and column positions provided. The only argument is an object with
  715. * the following properties:
  716. *
  717. * - source: The filename of the original source.
  718. * - line: The line number in the original source. The line number
  719. * is 1-based.
  720. * - column: The column number in the original source. The column
  721. * number is 0-based.
  722. * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
  723. * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
  724. * closest element that is smaller than or greater than the one we are
  725. * searching for, respectively, if the exact element cannot be found.
  726. * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
  727. *
  728. * and an object is returned with the following properties:
  729. *
  730. * - line: The line number in the generated source, or null. The
  731. * line number is 1-based.
  732. * - column: The column number in the generated source, or null.
  733. * The column number is 0-based.
  734. */
  735. BasicSourceMapConsumer.prototype.generatedPositionFor =
  736. function SourceMapConsumer_generatedPositionFor(aArgs) {
  737. var source = util.getArg(aArgs, 'source');
  738. source = this._findSourceIndex(source);
  739. if (source < 0) {
  740. return {
  741. line: null,
  742. column: null,
  743. lastColumn: null
  744. };
  745. }
  746. var needle = {
  747. source: source,
  748. originalLine: util.getArg(aArgs, 'line'),
  749. originalColumn: util.getArg(aArgs, 'column')
  750. };
  751. var index = this._findMapping(
  752. needle,
  753. this._originalMappings,
  754. "originalLine",
  755. "originalColumn",
  756. util.compareByOriginalPositions,
  757. util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND)
  758. );
  759. if (index >= 0) {
  760. var mapping = this._originalMappings[index];
  761. if (mapping.source === needle.source) {
  762. return {
  763. line: util.getArg(mapping, 'generatedLine', null),
  764. column: util.getArg(mapping, 'generatedColumn', null),
  765. lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
  766. };
  767. }
  768. }
  769. return {
  770. line: null,
  771. column: null,
  772. lastColumn: null
  773. };
  774. };
  775. exports.BasicSourceMapConsumer = BasicSourceMapConsumer;
  776. /**
  777. * An IndexedSourceMapConsumer instance represents a parsed source map which
  778. * we can query for information. It differs from BasicSourceMapConsumer in
  779. * that it takes "indexed" source maps (i.e. ones with a "sections" field) as
  780. * input.
  781. *
  782. * The first parameter is a raw source map (either as a JSON string, or already
  783. * parsed to an object). According to the spec for indexed source maps, they
  784. * have the following attributes:
  785. *
  786. * - version: Which version of the source map spec this map is following.
  787. * - file: Optional. The generated file this source map is associated with.
  788. * - sections: A list of section definitions.
  789. *
  790. * Each value under the "sections" field has two fields:
  791. * - offset: The offset into the original specified at which this section
  792. * begins to apply, defined as an object with a "line" and "column"
  793. * field.
  794. * - map: A source map definition. This source map could also be indexed,
  795. * but doesn't have to be.
  796. *
  797. * Instead of the "map" field, it's also possible to have a "url" field
  798. * specifying a URL to retrieve a source map from, but that's currently
  799. * unsupported.
  800. *
  801. * Here's an example source map, taken from the source map spec[0], but
  802. * modified to omit a section which uses the "url" field.
  803. *
  804. * {
  805. * version : 3,
  806. * file: "app.js",
  807. * sections: [{
  808. * offset: {line:100, column:10},
  809. * map: {
  810. * version : 3,
  811. * file: "section.js",
  812. * sources: ["foo.js", "bar.js"],
  813. * names: ["src", "maps", "are", "fun"],
  814. * mappings: "AAAA,E;;ABCDE;"
  815. * }
  816. * }],
  817. * }
  818. *
  819. * The second parameter, if given, is a string whose value is the URL
  820. * at which the source map was found. This URL is used to compute the
  821. * sources array.
  822. *
  823. * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.535es3xeprgt
  824. */
  825. function IndexedSourceMapConsumer(aSourceMap, aSourceMapURL) {
  826. var sourceMap = aSourceMap;
  827. if (typeof aSourceMap === 'string') {
  828. sourceMap = util.parseSourceMapInput(aSourceMap);
  829. }
  830. var version = util.getArg(sourceMap, 'version');
  831. var sections = util.getArg(sourceMap, 'sections');
  832. if (version != this._version) {
  833. throw new Error('Unsupported version: ' + version);
  834. }
  835. this._sources = new ArraySet();
  836. this._names = new ArraySet();
  837. var lastOffset = {
  838. line: -1,
  839. column: 0
  840. };
  841. var maxOffsetLine = 0;
  842. this._sections = sections.map(function (s) {
  843. if (s.url) {
  844. // The url field will require support for asynchronicity.
  845. // See https://github.com/mozilla/source-map/issues/16
  846. throw new Error('Support for url field in sections not implemented.');
  847. }
  848. var offset = util.getArg(s, 'offset');
  849. var offsetLine = util.getArg(offset, 'line');
  850. var offsetColumn = util.getArg(offset, 'column');
  851. if (!isValidOffset(offsetLine) || !isValidOffset(offsetColumn)) {
  852. throw new Error('Section offset line and column must be non-negative integers.');
  853. }
  854. if (offsetLine > MAX_SECTION_OFFSET_LINE) {
  855. throw new Error('Section offset line must not exceed ' + MAX_SECTION_OFFSET_LINE + '.');
  856. }
  857. if (offsetLine < lastOffset.line ||
  858. (offsetLine === lastOffset.line && offsetColumn < lastOffset.column)) {
  859. throw new Error('Section offsets must be ordered and non-overlapping.');
  860. }
  861. lastOffset = offset;
  862. var consumer = new SourceMapConsumer(util.getArg(s, 'map'), aSourceMapURL);
  863. // Offsets of a nested indexed map add up with this one, so apply the
  864. // bound to the total.
  865. var totalOffsetLine = offsetLine + (consumer._maxOffsetLine || 0);
  866. if (totalOffsetLine > MAX_SECTION_OFFSET_LINE) {
  867. throw new Error('Section offset line must not exceed ' + MAX_SECTION_OFFSET_LINE +
  868. ', including offsets of nested sections.');
  869. }
  870. if (totalOffsetLine > maxOffsetLine) {
  871. maxOffsetLine = totalOffsetLine;
  872. }
  873. return {
  874. generatedOffset: {
  875. // The offset fields are 0-based, but we use 1-based indices when
  876. // encoding/decoding from VLQ.
  877. generatedLine: offsetLine + 1,
  878. generatedColumn: offsetColumn + 1
  879. },
  880. consumer: consumer
  881. }
  882. });
  883. // The largest offset.line of any section, including nested sections.
  884. this._maxOffsetLine = maxOffsetLine;
  885. }
  886. /**
  887. * Section offsets come from untrusted input and are used as line/column
  888. * numbers, so only accept non-negative safe integers (rejects NaN, Infinity,
  889. * strings, fractions, etc).
  890. */
  891. function isValidOffset(aValue) {
  892. return typeof aValue === 'number' &&
  893. aValue >= 0 &&
  894. aValue <= 9007199254740991 && // Number.MAX_SAFE_INTEGER
  895. Math.floor(aValue) === aValue;
  896. }
  897. IndexedSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype);
  898. IndexedSourceMapConsumer.prototype.constructor = SourceMapConsumer;
  899. /**
  900. * The version of the source mapping spec that we are consuming.
  901. */
  902. IndexedSourceMapConsumer.prototype._version = 3;
  903. /**
  904. * The list of original sources.
  905. */
  906. Object.defineProperty(IndexedSourceMapConsumer.prototype, 'sources', {
  907. get: function () {
  908. var sources = [];
  909. for (var i = 0; i < this._sections.length; i++) {
  910. // Read the getter once per section: for a nested indexed map it is
  911. // itself this getter, and re-reading it per item is exponential in the
  912. // nesting depth (CVE-2026-93749).
  913. var sectionSources = this._sections[i].consumer.sources;
  914. for (var j = 0; j < sectionSources.length; j++) {
  915. sources.push(sectionSources[j]);
  916. }
  917. }
  918. return sources;
  919. }
  920. });
  921. /**
  922. * Returns the original source, line, and column information for the generated
  923. * source's line and column positions provided. The only argument is an object
  924. * with the following properties:
  925. *
  926. * - line: The line number in the generated source. The line number
  927. * is 1-based.
  928. * - column: The column number in the generated source. The column
  929. * number is 0-based.
  930. *
  931. * and an object is returned with the following properties:
  932. *
  933. * - source: The original source file, or null.
  934. * - line: The line number in the original source, or null. The
  935. * line number is 1-based.
  936. * - column: The column number in the original source, or null. The
  937. * column number is 0-based.
  938. * - name: The original identifier, or null.
  939. */
  940. IndexedSourceMapConsumer.prototype.originalPositionFor =
  941. function IndexedSourceMapConsumer_originalPositionFor(aArgs) {
  942. var needle = {
  943. generatedLine: util.getArg(aArgs, 'line'),
  944. generatedColumn: util.getArg(aArgs, 'column')
  945. };
  946. // Find the section containing the generated position we're trying to map
  947. // to an original position.
  948. var sectionIndex = binarySearch.search(needle, this._sections,
  949. function(needle, section) {
  950. var cmp = needle.generatedLine - section.generatedOffset.generatedLine;
  951. if (cmp) {
  952. return cmp;
  953. }
  954. return (needle.generatedColumn -
  955. section.generatedOffset.generatedColumn);
  956. });
  957. var section = this._sections[sectionIndex];
  958. if (!section) {
  959. return {
  960. source: null,
  961. line: null,
  962. column: null,
  963. name: null
  964. };
  965. }
  966. return section.consumer.originalPositionFor({
  967. line: needle.generatedLine -
  968. (section.generatedOffset.generatedLine - 1),
  969. column: needle.generatedColumn -
  970. (section.generatedOffset.generatedLine === needle.generatedLine
  971. ? section.generatedOffset.generatedColumn - 1
  972. : 0),
  973. bias: aArgs.bias
  974. });
  975. };
  976. /**
  977. * Return true if we have the source content for every source in the source
  978. * map, false otherwise.
  979. */
  980. IndexedSourceMapConsumer.prototype.hasContentsOfAllSources =
  981. function IndexedSourceMapConsumer_hasContentsOfAllSources() {
  982. return this._sections.every(function (s) {
  983. return s.consumer.hasContentsOfAllSources();
  984. });
  985. };
  986. /**
  987. * Returns the original source content. The only argument is the url of the
  988. * original source file. Returns null if no original source content is
  989. * available.
  990. */
  991. IndexedSourceMapConsumer.prototype.sourceContentFor =
  992. function IndexedSourceMapConsumer_sourceContentFor(aSource, nullOnMissing) {
  993. for (var i = 0; i < this._sections.length; i++) {
  994. var section = this._sections[i];
  995. var content = section.consumer.sourceContentFor(aSource, true);
  996. if (content || content === '') {
  997. return content;
  998. }
  999. }
  1000. if (nullOnMissing) {
  1001. return null;
  1002. }
  1003. else {
  1004. throw new Error('"' + aSource + '" is not in the SourceMap.');
  1005. }
  1006. };
  1007. /**
  1008. * Returns the generated line and column information for the original source,
  1009. * line, and column positions provided. The only argument is an object with
  1010. * the following properties:
  1011. *
  1012. * - source: The filename of the original source.
  1013. * - line: The line number in the original source. The line number
  1014. * is 1-based.
  1015. * - column: The column number in the original source. The column
  1016. * number is 0-based.
  1017. *
  1018. * and an object is returned with the following properties:
  1019. *
  1020. * - line: The line number in the generated source, or null. The
  1021. * line number is 1-based.
  1022. * - column: The column number in the generated source, or null.
  1023. * The column number is 0-based.
  1024. */
  1025. IndexedSourceMapConsumer.prototype.generatedPositionFor =
  1026. function IndexedSourceMapConsumer_generatedPositionFor(aArgs) {
  1027. for (var i = 0; i < this._sections.length; i++) {
  1028. var section = this._sections[i];
  1029. // Only consider this section if the requested source is in the list of
  1030. // sources of the consumer.
  1031. if (section.consumer._findSourceIndex(util.getArg(aArgs, 'source')) === -1) {
  1032. continue;
  1033. }
  1034. var generatedPosition = section.consumer.generatedPositionFor(aArgs);
  1035. if (generatedPosition) {
  1036. var ret = {
  1037. line: generatedPosition.line +
  1038. (section.generatedOffset.generatedLine - 1),
  1039. column: generatedPosition.column +
  1040. (section.generatedOffset.generatedLine === generatedPosition.line
  1041. ? section.generatedOffset.generatedColumn - 1
  1042. : 0)
  1043. };
  1044. return ret;
  1045. }
  1046. }
  1047. return {
  1048. line: null,
  1049. column: null
  1050. };
  1051. };
  1052. /**
  1053. * Parse the mappings in a string in to a data structure which we can easily
  1054. * query (the ordered arrays in the `this.__generatedMappings` and
  1055. * `this.__originalMappings` properties).
  1056. */
  1057. IndexedSourceMapConsumer.prototype._parseMappings =
  1058. function IndexedSourceMapConsumer_parseMappings(aStr, aSourceRoot) {
  1059. this.__generatedMappings = [];
  1060. this.__originalMappings = [];
  1061. for (var i = 0; i < this._sections.length; i++) {
  1062. var section = this._sections[i];
  1063. var sectionMappings = section.consumer._generatedMappings;
  1064. for (var j = 0; j < sectionMappings.length; j++) {
  1065. var mapping = sectionMappings[j];
  1066. var source = section.consumer._sources.at(mapping.source);
  1067. if(source !== null) {
  1068. source = util.computeSourceURL(section.consumer.sourceRoot, source, this._sourceMapURL);
  1069. }
  1070. this._sources.add(source);
  1071. source = this._sources.indexOf(source);
  1072. var name = null;
  1073. if (mapping.name) {
  1074. name = section.consumer._names.at(mapping.name);
  1075. this._names.add(name);
  1076. name = this._names.indexOf(name);
  1077. }
  1078. // The mappings coming from the consumer for the section have
  1079. // generated positions relative to the start of the section, so we
  1080. // need to offset them to be relative to the start of the concatenated
  1081. // generated file.
  1082. var adjustedMapping = {
  1083. source: source,
  1084. generatedLine: mapping.generatedLine +
  1085. (section.generatedOffset.generatedLine - 1),
  1086. generatedColumn: mapping.generatedColumn +
  1087. (section.generatedOffset.generatedLine === mapping.generatedLine
  1088. ? section.generatedOffset.generatedColumn - 1
  1089. : 0),
  1090. originalLine: mapping.originalLine,
  1091. originalColumn: mapping.originalColumn,
  1092. name: name
  1093. };
  1094. this.__generatedMappings.push(adjustedMapping);
  1095. if (typeof adjustedMapping.originalLine === 'number') {
  1096. this.__originalMappings.push(adjustedMapping);
  1097. }
  1098. }
  1099. }
  1100. quickSort(this.__generatedMappings, util.compareByGeneratedPositionsDeflated);
  1101. quickSort(this.__originalMappings, util.compareByOriginalPositions);
  1102. };
  1103. exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer;