• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// This file is a modified version of:
2// https://cs.chromium.org/chromium/src/v8/tools/SourceMap.js?rcl=dd10454c1d
3// from the V8 codebase. Logic specific to WebInspector is removed and linting
4// is made to match the Node.js style guide.
5
6// Copyright 2013 the V8 project authors. All rights reserved.
7// Redistribution and use in source and binary forms, with or without
8// modification, are permitted provided that the following conditions are
9// met:
10//
11//     * Redistributions of source code must retain the above copyright
12//       notice, this list of conditions and the following disclaimer.
13//     * Redistributions in binary form must reproduce the above
14//       copyright notice, this list of conditions and the following
15//       disclaimer in the documentation and/or other materials provided
16//       with the distribution.
17//     * Neither the name of Google Inc. nor the names of its
18//       contributors may be used to endorse or promote products derived
19//       from this software without specific prior written permission.
20//
21// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
33// This is a copy from blink dev tools, see:
34// http://src.chromium.org/viewvc/blink/trunk/Source/devtools/front_end/SourceMap.js
35// revision: 153407
36
37/*
38 * Copyright (C) 2012 Google Inc. All rights reserved.
39 *
40 * Redistribution and use in source and binary forms, with or without
41 * modification, are permitted provided that the following conditions are
42 * met:
43 *
44 *     * Redistributions of source code must retain the above copyright
45 * notice, this list of conditions and the following disclaimer.
46 *     * Redistributions in binary form must reproduce the above
47 * copyright notice, this list of conditions and the following disclaimer
48 * in the documentation and/or other materials provided with the
49 * distribution.
50 *     * Neither the name of Google Inc. nor the names of its
51 * contributors may be used to endorse or promote products derived from
52 * this software without specific prior written permission.
53 *
54 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
55 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
56 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
57 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
58 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
59 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
60 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
61 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
62 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
63 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
64 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
65 */
66
67static std::string SourceMapRunner = R"JS(
68let base64Map;
69
70const VLQ_BASE_SHIFT = 5;
71const VLQ_BASE_MASK = (1 << 5) - 1;
72const VLQ_CONTINUATION_MASK = 1 << 5;
73
74class StringCharIterator {
75  /**
76   * @constructor
77   * @param {string} string
78   */
79  constructor(string) {
80    this._string = string;
81    this._position = 0;
82  }
83
84  /**
85   * @return {string}
86   */
87  next() {
88    return this._string.charAt(this._position++);
89  }
90
91  /**
92   * @return {string}
93   */
94  peek() {
95    return this._string.charAt(this._position);
96  }
97
98  /**
99   * @return {boolean}
100   */
101  hasNext() {
102    return this._position < this._string.length;
103  }
104}
105
106/**
107 * Implements Source Map V3 model.
108 * See https://github.com/google/closure-compiler/wiki/Source-Maps
109 * for format description.
110 */
111class SourceMap {
112  #payload;
113  #mappings = [];
114  #sources = {};
115  #sourceContentByURL = {};
116
117  /**
118   * @constructor
119   * @param {SourceMapV3} payload
120   */
121  constructor(payload) {
122    if (!base64Map) {
123      const base64Digits =
124             'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
125      base64Map = {};
126      for (let i = 0; i < base64Digits.length; ++i)
127        base64Map[base64Digits[i]] = i;
128    }
129    this.#payload = cloneSourceMapV3(payload);
130    this.#parseMappingPayload();
131  }
132
133  /**
134   * @return {object} raw source map v3 payload.
135   */
136  get payload() {
137    return cloneSourceMapV3(this.#payload);
138  }
139
140  #parseMappingPayload = () => {
141    if (this.#payload.sections) {
142      this.#parseSections(this.#payload.sections);
143    } else {
144      this.#parseMap(this.#payload, 0, 0);
145    }
146    this.#mappings.sort(compareSourceMapEntry);
147  };
148
149  /**
150   * @param {Array.<SourceMapV3.Section>} sections
151   */
152  #parseSections = (sections) => {
153    for (let i = 0; i < sections.length; ++i) {
154      const section = sections[i];
155      this.#parseMap(section.map, section.offset.line, section.offset.column);
156    }
157  };
158
159  /**
160   * @param {number} lineOffset 0-indexed line offset in compiled resource
161   * @param {number} columnOffset 0-indexed column offset in compiled resource
162   * @return {object} representing start of range if found, or empty object
163   */
164  findEntry(lineOffset, columnOffset) {
165    let first = 0;
166    let count = this.#mappings.length;
167    while (count > 1) {
168      const step = count >> 1;
169      const middle = first + step;
170      const mapping = this.#mappings[middle];
171      if (lineOffset < mapping[0] ||
172          (lineOffset === mapping[0] && columnOffset < mapping[1])) {
173        count = step;
174      } else {
175        first = middle;
176        count -= step;
177      }
178    }
179    const entry = this.#mappings[first];
180    if (!first && entry && (lineOffset < entry[0] ||
181        (lineOffset === entry[0] && columnOffset < entry[1]))) {
182      return {};
183    } else if (!entry) {
184      return {};
185    }
186    return {
187      generatedLine: entry[0],
188      generatedColumn: entry[1],
189      originalSource: entry[2],
190      originalLine: entry[3],
191      originalColumn: entry[4],
192      name: entry[5],
193    };
194  }
195
196  /**
197   * @param {number} lineNumber 1-indexed line number in compiled resource call site
198   * @param {number} columnNumber 1-indexed column number in compiled resource call site
199   * @return {object} representing origin call site if found, or empty object
200   */
201  findOrigin(lineNumber, columnNumber) {
202    const range = this.findEntry(lineNumber - 1, columnNumber - 1);
203    if (
204      range.originalSource === undefined ||
205      range.originalLine === undefined ||
206      range.originalColumn === undefined ||
207      range.generatedLine === undefined ||
208      range.generatedColumn === undefined
209    ) {
210      return {};
211    }
212    const lineOffset = lineNumber - range.generatedLine;
213    const columnOffset = columnNumber - range.generatedColumn;
214    return {
215      name: range.name,
216      fileName: range.originalSource,
217      lineNumber: range.originalLine + lineOffset,
218      columnNumber: range.originalColumn + columnOffset,
219    };
220  }
221
222  /**
223   * @override
224   */
225  #parseMap(map, lineNumber, columnNumber) {
226    let sourceIndex = 0;
227    let sourceLineNumber = 0;
228    let sourceColumnNumber = 0;
229    let nameIndex = 0;
230
231    const sources = [];
232    const originalToCanonicalURLMap = {};
233    for (let i = 0; i < map.sources.length; ++i) {
234      const url = map.sources[i];
235      originalToCanonicalURLMap[url] = url;
236      sources.push(url);
237      this.#sources[url] = true;
238
239      if (map.sourcesContent && map.sourcesContent[i])
240        this.#sourceContentByURL[url] = map.sourcesContent[i];
241    }
242
243    const stringCharIterator = new StringCharIterator(map.mappings);
244    let sourceURL = sources[sourceIndex];
245    while (true) {
246      if (stringCharIterator.peek() === ',')
247        stringCharIterator.next();
248      else {
249        while (stringCharIterator.peek() === ';') {
250          lineNumber += 1;
251          columnNumber = 0;
252          stringCharIterator.next();
253        }
254        if (!stringCharIterator.hasNext())
255          break;
256      }
257
258      columnNumber += decodeVLQ(stringCharIterator);
259      if (isSeparator(stringCharIterator.peek())) {
260        this.#mappings.push([lineNumber, columnNumber]);
261        continue;
262      }
263
264      const sourceIndexDelta = decodeVLQ(stringCharIterator);
265      if (sourceIndexDelta) {
266        sourceIndex += sourceIndexDelta;
267        sourceURL = sources[sourceIndex];
268      }
269      sourceLineNumber += decodeVLQ(stringCharIterator);
270      sourceColumnNumber += decodeVLQ(stringCharIterator);
271
272      let name;
273      if (!isSeparator(stringCharIterator.peek())) {
274        nameIndex += decodeVLQ(stringCharIterator);
275        name = map.names?.[nameIndex];
276      }
277
278      this.#mappings.push([
279        lineNumber, columnNumber, sourceURL, sourceLineNumber,
280        sourceColumnNumber, name],);
281    }
282  }
283}
284
285/**
286 * @param {string} char
287 * @return {boolean}
288 */
289function isSeparator(char) {
290  return char === ',' || char === ';';
291}
292
293/**
294 * @param {SourceMap.StringCharIterator} stringCharIterator
295 * @return {number}
296 */
297function decodeVLQ(stringCharIterator) {
298  // Read unsigned value.
299  let result = 0;
300  let shift = 0;
301  let digit;
302  do {
303    digit = base64Map[stringCharIterator.next()];
304    result += (digit & VLQ_BASE_MASK) << shift;
305    shift += VLQ_BASE_SHIFT;
306  } while (digit & VLQ_CONTINUATION_MASK);
307
308  // Fix the sign.
309  const negative = result & 1;
310  // Use unsigned right shift, so that the 32nd bit is properly shifted to the
311  // 31st, and the 32nd becomes unset.
312  result >>>= 1;
313  if (!negative) {
314    return result;
315  }
316
317  // We need to OR here to ensure the 32nd bit (the sign bit in an Int32) is
318  // always set for negative numbers. If `result` were 1, (meaning `negate` is
319  // true and all other bits were zeros), `result` would now be 0. But -0
320  // doesn't flip the 32nd bit as intended. All other numbers will successfully
321  // set the 32nd bit without issue, so doing this is a noop for them.
322  return -result | (1 << 31);
323}
324
325/**
326 * @param {SourceMapV3} payload
327 * @return {SourceMapV3}
328 */
329function cloneSourceMapV3(payload) {
330  payload = { ...payload };
331  for (const key in payload) {
332    if (Object.prototype.hasOwnProperty.call(payload, key) &&
333        Array.isArray(payload[key])) {
334      payload[key] = payload[key].slice();
335    }
336  }
337  return payload;
338}
339
340/**
341 * @param {Array} entry1 source map entry [lineNumber, columnNumber, sourceURL,
342 *  sourceLineNumber, sourceColumnNumber]
343 * @param {Array} entry2 source map entry.
344 * @return {number}
345 */
346function compareSourceMapEntry(entry1, entry2) {
347  const { 0: lineNumber1, 1: columnNumber1 } = entry1;
348  const { 0: lineNumber2, 1: columnNumber2 } = entry2;
349  if (lineNumber1 !== lineNumber2) {
350    return lineNumber1 - lineNumber2;
351  }
352  return columnNumber1 - columnNumber2;
353}
354
355result = function(error, trace, sourceMap) {
356  try {
357    const payload = JSON.parse(sourceMap)
358    const sm = new SourceMap(payload)
359    const preparedStack = trace.map((t, i) => {
360      const str = i !== 0 ? '\n    at ' : '';
361      const {
362        originalLine,
363        originalColumn,
364        originalSource,
365      } = sm.findEntry(t.getLineNumber() - 1, t.getColumnNumber() - 1);
366      if (originalSource && originalLine !== undefined &&
367          originalColumn !== undefined) {
368        // A stack trace will often have several call sites in a row within the
369        // same file, cache the source map and file content accordingly:
370        let fileName = t.getFileName();
371        if (fileName === undefined) {
372          fileName = t.getEvalOrigin();
373        }
374        const fnName = t.getFunctionName() ?? t.getMethodName();
375        const typeName = t.getTypeName();
376        const namePrefix = typeName !== null && typeName !== 'global' ? `${typeName}.` : '';
377        const originalName = `${namePrefix}${fnName || '<anonymous>'}`;
378        const hasName = !!originalName;
379        return `${str}${originalName}${hasName ? ' (' : ''}` +
380          `${originalSource}:${originalLine + 1}:` +
381          `${originalColumn + 1}${hasName ? ')' : ''}`;
382      }
383      return `${str}${t}`
384    }).join('');
385    return `${error}\n    at ${preparedStack}`
386  } catch(e) {
387    const originStack = trace.map((t, i) => {
388      const str = i !== 0 ? '\n    at ' : '';
389      return `${str}${t}`
390    }).join('')
391    return `${error}\n    at ${originStack}`
392  }
393}
394)JS";
395