Upload Kmake

This commit is contained in:
Gorochu
2026-05-26 23:36:42 -07:00
parent ba051b2f74
commit 555ec72358
41615 changed files with 13344630 additions and 1 deletions

View File

@ -0,0 +1,248 @@
'use strict';
const {
ArrayPrototypeIndexOf,
ArrayPrototypeJoin,
ArrayPrototypeMap,
ErrorPrototypeToString,
RegExpPrototypeSymbolSplit,
SafeStringIterator,
StringPrototypeRepeat,
StringPrototypeSlice,
StringPrototypeStartsWith,
} = primordials;
let debug = require('internal/util/debuglog').debuglog('source_map', (fn) => {
debug = fn;
});
const { getStringWidth } = require('internal/util/inspect');
const { readFileSync } = require('fs');
const { findSourceMap } = require('internal/source_map/source_map_cache');
const {
kIsNodeError,
} = require('internal/errors');
const { fileURLToPath } = require('internal/url');
const { setGetSourceMapErrorSource } = internalBinding('errors');
const kStackLineAt = '\n at ';
// Create a prettified stacktrace, inserting context from source maps
// if possible.
function prepareStackTraceWithSourceMaps(error, trace) {
let errorString;
if (kIsNodeError in error) {
errorString = `${error.name} [${error.code}]: ${error.message}`;
} else {
errorString = ErrorPrototypeToString(error);
}
if (trace.length === 0) {
return errorString;
}
let lastSourceMap;
let lastFileName;
const preparedTrace = ArrayPrototypeJoin(ArrayPrototypeMap(trace, (callSite, i) => {
try {
// A stack trace will often have several call sites in a row within the
// same file, cache the source map and file content accordingly:
let fileName = callSite.getFileName();
if (fileName === undefined) {
fileName = callSite.getEvalOrigin();
}
const sm = fileName === lastFileName ?
lastSourceMap :
findSourceMap(fileName);
// Only when a source map is found, cache it for the next iteration.
// This is a performance optimization to avoid interleaving with JS builtin function
// invalidating the cache.
// - at myFunc (file:///path/to/file.js:1:2)
// - at Array.map (<anonymous>)
// - at myFunc (file:///path/to/file.js:3:4)
if (sm) {
lastSourceMap = sm;
lastFileName = fileName;
return `${kStackLineAt}${serializeJSStackFrame(sm, callSite, trace[i + 1])}`;
}
} catch (err) {
debug(err);
}
return `${kStackLineAt}${callSite}`;
}), '');
return `${errorString}${preparedTrace}`;
}
/**
* Serialize a single call site in the stack trace.
* Refer to SerializeJSStackFrame in deps/v8/src/objects/call-site-info.cc for
* more details about the default ToString(CallSite).
* The CallSite API is documented at https://v8.dev/docs/stack-trace-api.
* @param {import('internal/source_map/source_map').SourceMap} sm
* @param {CallSite} callSite - the CallSite object to be serialized
* @param {CallSite} callerCallSite - caller site info
* @returns {string} - the serialized call site
*/
function serializeJSStackFrame(sm, callSite, callerCallSite) {
// Source Map V3 lines/columns start at 0/0 whereas stack traces
// start at 1/1:
const {
originalLine,
originalColumn,
originalSource,
} = sm.findEntry(callSite.getLineNumber() - 1, callSite.getColumnNumber() - 1);
if (originalSource === undefined || originalLine === undefined ||
originalColumn === undefined) {
return `${callSite}`;
}
const name = getOriginalSymbolName(sm, callSite, callerCallSite);
const originalSourceNoScheme =
StringPrototypeStartsWith(originalSource, 'file://') ?
fileURLToPath(originalSource) : originalSource;
// Construct call site name based on: v8.dev/docs/stack-trace-api:
const fnName = callSite.getFunctionName() ?? callSite.getMethodName();
let prefix = '';
if (callSite.isAsync()) {
// Promise aggregation operation frame has no locations. This must be an
// async stack frame.
prefix = 'async ';
} else if (callSite.isConstructor()) {
prefix = 'new ';
}
const typeName = callSite.getTypeName();
const namePrefix = typeName !== null && typeName !== 'global' ? `${typeName}.` : '';
const originalName = `${namePrefix}${fnName || '<anonymous>'}`;
// The original call site may have a different symbol name
// associated with it, use it:
const mappedName = (name && name !== originalName) ?
`${name}` :
`${originalName}`;
const hasName = !!(name || originalName);
// Replace the transpiled call site with the original:
return `${prefix}${mappedName}${hasName ? ' (' : ''}` +
`${originalSourceNoScheme}:${originalLine + 1}:` +
`${originalColumn + 1}${hasName ? ')' : ''}`;
}
// Transpilers may have removed the original symbol name used in the stack
// trace, if possible restore it from the names field of the source map:
function getOriginalSymbolName(sourceMap, callSite, callerCallSite) {
// First check for a symbol name associated with the enclosing function:
const enclosingEntry = sourceMap.findEntry(
callSite.getEnclosingLineNumber() - 1,
callSite.getEnclosingColumnNumber() - 1,
);
if (enclosingEntry.name) return enclosingEntry.name;
// Fallback to using the symbol name attached to the caller site:
const currentFileName = callSite.getFileName();
if (callerCallSite && currentFileName === callerCallSite.getFileName()) {
const { name } = sourceMap.findEntry(
callerCallSite.getLineNumber() - 1,
callerCallSite.getColumnNumber() - 1,
);
return name;
}
}
/**
* Return a snippet of code from where the exception was originally thrown
* above the stack trace. This called from GetErrorSource in node_errors.cc.
* @param {import('internal/source_map/source_map').SourceMap} sourceMap - the source map to be used
* @param {string} originalSourcePath - path or url of the original source
* @param {number} originalLine - line number in the original source
* @param {number} originalColumn - column number in the original source
* @returns {string | undefined} - the exact line in the source content or undefined if file not found
*/
function getErrorSource(
sourceMap,
originalSourcePath,
originalLine,
originalColumn,
) {
const originalSourcePathNoScheme =
StringPrototypeStartsWith(originalSourcePath, 'file://') ?
fileURLToPath(originalSourcePath) : originalSourcePath;
const source = getOriginalSource(
sourceMap.payload,
originalSourcePath,
);
if (typeof source !== 'string') {
return;
}
const lines = RegExpPrototypeSymbolSplit(/\r?\n/, source, originalLine + 1);
const line = lines[originalLine];
if (!line) {
return;
}
// Display ^ in appropriate position, regardless of whether tabs or
// spaces are used:
let prefix = '';
for (const character of new SafeStringIterator(
StringPrototypeSlice(line, 0, originalColumn + 1))) {
prefix += character === '\t' ? '\t' :
StringPrototypeRepeat(' ', getStringWidth(character));
}
prefix = StringPrototypeSlice(prefix, 0, -1); // The last character is '^'.
const exceptionLine =
`${originalSourcePathNoScheme}:${originalLine + 1}\n${line}\n${prefix}^\n\n`;
return exceptionLine;
}
/**
* Retrieve the original source code from the source map's `sources` list or disk.
* @param {import('internal/source_map/source_map').SourceMap.payload} payload
* @param {string} originalSourcePath - path or url of the original source
* @returns {string | undefined} - the source content or undefined if file not found
*/
function getOriginalSource(payload, originalSourcePath) {
let source;
// payload.sources has been normalized to be an array of absolute urls.
const sourceContentIndex =
ArrayPrototypeIndexOf(payload.sources, originalSourcePath);
if (payload.sourcesContent?.[sourceContentIndex]) {
// First we check if the original source content was provided in the
// source map itself:
source = payload.sourcesContent[sourceContentIndex];
} else if (StringPrototypeStartsWith(originalSourcePath, 'file://')) {
// If no sourcesContent was found, attempt to load the original source
// from disk:
debug(`read source of ${originalSourcePath} from filesystem`);
const originalSourcePathNoScheme = fileURLToPath(originalSourcePath);
try {
source = readFileSync(originalSourcePathNoScheme, 'utf8');
} catch (err) {
debug(err);
}
}
return source;
}
/**
* Retrieve exact line in the original source code from the source map's `sources` list or disk.
* @param {string} fileName - actual file name
* @param {number} lineNumber - actual line number
* @param {number} columnNumber - actual column number
* @returns {string | undefined} - the source content or undefined if file not found
*/
function getSourceMapErrorSource(fileName, lineNumber, columnNumber) {
const sm = findSourceMap(fileName);
if (sm === undefined) {
return;
}
const {
originalLine,
originalColumn,
originalSource,
} = sm.findEntry(lineNumber - 1, columnNumber);
const errorSource = getErrorSource(sm, originalSource, originalLine, originalColumn);
return errorSource;
}
setGetSourceMapErrorSource(getSourceMapErrorSource);
module.exports = {
prepareStackTraceWithSourceMaps,
};

View File

@ -0,0 +1,394 @@
// This file is a modified version of:
// https://cs.chromium.org/chromium/src/v8/tools/SourceMap.js?rcl=dd10454c1d
// from the V8 codebase. Logic specific to WebInspector is removed and linting
// is made to match the Node.js style guide.
// Copyright 2013 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// This is a copy from blink dev tools, see:
// http://src.chromium.org/viewvc/blink/trunk/Source/devtools/front_end/SourceMap.js
// revision: 153407
/*
* Copyright (C) 2012 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
'use strict';
const {
ArrayIsArray,
ArrayPrototypePush,
ArrayPrototypeSlice,
ArrayPrototypeSort,
ObjectPrototypeHasOwnProperty,
StringPrototypeCharAt,
Symbol,
} = primordials;
const { validateObject } = require('internal/validators');
let base64Map;
const VLQ_BASE_SHIFT = 5;
const VLQ_BASE_MASK = (1 << 5) - 1;
const VLQ_CONTINUATION_MASK = 1 << 5;
const kMappings = Symbol('kMappings');
class StringCharIterator {
/**
* @constructor
* @param {string} string
*/
constructor(string) {
this._string = string;
this._position = 0;
}
/**
* @return {string}
*/
next() {
return StringPrototypeCharAt(this._string, this._position++);
}
/**
* @return {string}
*/
peek() {
return StringPrototypeCharAt(this._string, this._position);
}
/**
* @return {boolean}
*/
hasNext() {
return this._position < this._string.length;
}
}
/**
* Implements Source Map V3 model.
* See https://github.com/google/closure-compiler/wiki/Source-Maps
* for format description.
*/
class SourceMap {
#payload;
#mappings = [];
#sources = {};
#sourceContentByURL = {};
#lineLengths = undefined;
/**
* @constructor
* @param {SourceMapV3} payload
*/
constructor(payload, { lineLengths } = { __proto__: null }) {
if (!base64Map) {
const base64Digits =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
base64Map = {};
for (let i = 0; i < base64Digits.length; ++i)
base64Map[base64Digits[i]] = i;
}
this.#payload = cloneSourceMapV3(payload);
this.#parseMappingPayload();
if (ArrayIsArray(lineLengths) && lineLengths.length) {
this.#lineLengths = lineLengths;
}
}
/**
* @return {object} raw source map v3 payload.
*/
get payload() {
return cloneSourceMapV3(this.#payload);
}
get [kMappings]() {
return this.#mappings;
}
/**
* @return {number[] | undefined} line lengths of generated source code
*/
get lineLengths() {
if (this.#lineLengths) {
return ArrayPrototypeSlice(this.#lineLengths);
}
return undefined;
}
#parseMappingPayload = () => {
if (this.#payload.sections) {
this.#parseSections(this.#payload.sections);
} else {
this.#parseMap(this.#payload, 0, 0);
}
ArrayPrototypeSort(this.#mappings, compareSourceMapEntry);
};
/**
* @param {Array.<SourceMapV3.Section>} sections
*/
#parseSections = (sections) => {
for (let i = 0; i < sections.length; ++i) {
const section = sections[i];
this.#parseMap(section.map, section.offset.line, section.offset.column);
}
};
/**
* @param {number} lineOffset 0-indexed line offset in compiled resource
* @param {number} columnOffset 0-indexed column offset in compiled resource
* @return {object} representing start of range if found, or empty object
*/
findEntry(lineOffset, columnOffset) {
let first = 0;
let count = this.#mappings.length;
while (count > 1) {
const step = count >> 1;
const middle = first + step;
const mapping = this.#mappings[middle];
if (lineOffset < mapping[0] ||
(lineOffset === mapping[0] && columnOffset < mapping[1])) {
count = step;
} else {
first = middle;
count -= step;
}
}
const entry = this.#mappings[first];
if (!first && entry && (lineOffset < entry[0] ||
(lineOffset === entry[0] && columnOffset < entry[1]))) {
return {};
} else if (!entry) {
return {};
}
return {
generatedLine: entry[0],
generatedColumn: entry[1],
originalSource: entry[2],
originalLine: entry[3],
originalColumn: entry[4],
name: entry[5],
};
}
/**
* @param {number} lineNumber 1-indexed line number in compiled resource call site
* @param {number} columnNumber 1-indexed column number in compiled resource call site
* @return {object} representing origin call site if found, or empty object
*/
findOrigin(lineNumber, columnNumber) {
const range = this.findEntry(lineNumber - 1, columnNumber - 1);
if (
range.originalSource === undefined ||
range.originalLine === undefined ||
range.originalColumn === undefined ||
range.generatedLine === undefined ||
range.generatedColumn === undefined
) {
return {};
}
const lineOffset = lineNumber - range.generatedLine;
const columnOffset = columnNumber - range.generatedColumn;
return {
name: range.name,
fileName: range.originalSource,
lineNumber: range.originalLine + lineOffset,
columnNumber: range.originalColumn + columnOffset,
};
}
/**
* @override
*/
#parseMap(map, lineNumber, columnNumber) {
let sourceIndex = 0;
let sourceLineNumber = 0;
let sourceColumnNumber = 0;
let nameIndex = 0;
const sources = [];
const originalToCanonicalURLMap = {};
for (let i = 0; i < map.sources.length; ++i) {
const url = map.sources[i];
originalToCanonicalURLMap[url] = url;
ArrayPrototypePush(sources, url);
this.#sources[url] = true;
if (map.sourcesContent?.[i])
this.#sourceContentByURL[url] = map.sourcesContent[i];
}
const stringCharIterator = new StringCharIterator(map.mappings);
let sourceURL = sources[sourceIndex];
while (true) {
if (stringCharIterator.peek() === ',')
stringCharIterator.next();
else {
while (stringCharIterator.peek() === ';') {
lineNumber += 1;
columnNumber = 0;
stringCharIterator.next();
}
if (!stringCharIterator.hasNext())
break;
}
columnNumber += decodeVLQ(stringCharIterator);
if (isSeparator(stringCharIterator.peek())) {
ArrayPrototypePush(this.#mappings, [lineNumber, columnNumber]);
continue;
}
const sourceIndexDelta = decodeVLQ(stringCharIterator);
if (sourceIndexDelta) {
sourceIndex += sourceIndexDelta;
sourceURL = sources[sourceIndex];
}
sourceLineNumber += decodeVLQ(stringCharIterator);
sourceColumnNumber += decodeVLQ(stringCharIterator);
let name;
if (!isSeparator(stringCharIterator.peek())) {
nameIndex += decodeVLQ(stringCharIterator);
name = map.names?.[nameIndex];
}
ArrayPrototypePush(
this.#mappings,
[lineNumber, columnNumber, sourceURL, sourceLineNumber,
sourceColumnNumber, name],
);
}
}
}
/**
* @param {string} char
* @return {boolean}
*/
function isSeparator(char) {
return char === ',' || char === ';';
}
/**
* @param {SourceMap.StringCharIterator} stringCharIterator
* @return {number}
*/
function decodeVLQ(stringCharIterator) {
// Read unsigned value.
let result = 0;
let shift = 0;
let digit;
do {
digit = base64Map[stringCharIterator.next()];
result += (digit & VLQ_BASE_MASK) << shift;
shift += VLQ_BASE_SHIFT;
} while (digit & VLQ_CONTINUATION_MASK);
// Fix the sign.
const negative = result & 1;
// Use unsigned right shift, so that the 32nd bit is properly shifted to the
// 31st, and the 32nd becomes unset.
result >>>= 1;
if (!negative) {
return result;
}
// We need to OR here to ensure the 32nd bit (the sign bit in an Int32) is
// always set for negative numbers. If `result` were 1, (meaning `negate` is
// true and all other bits were zeros), `result` would now be 0. But -0
// doesn't flip the 32nd bit as intended. All other numbers will successfully
// set the 32nd bit without issue, so doing this is a noop for them.
return -result | (1 << 31);
}
/**
* @param {SourceMapV3} payload
* @return {SourceMapV3}
*/
function cloneSourceMapV3(payload) {
validateObject(payload, 'payload');
payload = { ...payload };
for (const key in payload) {
if (ObjectPrototypeHasOwnProperty(payload, key) &&
ArrayIsArray(payload[key])) {
payload[key] = ArrayPrototypeSlice(payload[key]);
}
}
return payload;
}
/**
* @param {Array} entry1 source map entry [lineNumber, columnNumber, sourceURL,
* sourceLineNumber, sourceColumnNumber]
* @param {Array} entry2 source map entry.
* @return {number}
*/
function compareSourceMapEntry(entry1, entry2) {
const { 0: lineNumber1, 1: columnNumber1 } = entry1;
const { 0: lineNumber2, 1: columnNumber2 } = entry2;
if (lineNumber1 !== lineNumber2) {
return lineNumber1 - lineNumber2;
}
return columnNumber1 - columnNumber2;
}
module.exports = {
kMappings,
SourceMap,
};

View File

@ -0,0 +1,410 @@
'use strict';
const {
ArrayPrototypePush,
JSONParse,
ObjectFreeze,
RegExpPrototypeExec,
SafeMap,
StringPrototypeCodePointAt,
StringPrototypeSplit,
} = primordials;
// See https://tc39.es/ecma426/ for SourceMap V3 specification.
const { Buffer } = require('buffer');
let debug = require('internal/util/debuglog').debuglog('source_map', (fn) => {
debug = fn;
});
const { validateBoolean, validateObject } = require('internal/validators');
const {
setSourceMapsEnabled: setSourceMapsNative,
} = internalBinding('errors');
const {
defaultPrepareStackTrace,
setInternalPrepareStackTrace,
} = require('internal/errors');
const { getLazy, isUnderNodeModules, kEmptyObject } = require('internal/util');
const getModuleSourceMapCache = getLazy(() => {
const { SourceMapCacheMap } = require('internal/source_map/source_map_cache_map');
return new SourceMapCacheMap();
});
// The generated source module/script instance is not accessible, so we can use
// a Map without memory concerns. Separate generated source entries with the module
// source entries to avoid overriding the module source entries with arbitrary
// source url magic comments.
const generatedSourceMapCache = new SafeMap();
const kLeadingProtocol = /^\w+:\/\//;
const kSourceMappingURLMagicComment = /\/[*/]#\s+sourceMappingURL=(?<sourceMappingURL>[^\s]+)/g;
const kSourceURLMagicComment = /\/[*/]#\s+sourceURL=(?<sourceURL>[^\s]+)/g;
const { isAbsolute } = require('path');
const { fileURLToPath, pathToFileURL, URL, URLParse } = require('internal/url');
let SourceMap;
// This is configured with --enable-source-maps during pre-execution.
let sourceMapsSupport = ObjectFreeze({
__proto__: null,
enabled: false,
nodeModules: false,
generatedCode: false,
});
function getSourceMapsSupport() {
// Return a read-only object.
return sourceMapsSupport;
}
/**
* Enables or disables source maps programmatically.
* @param {boolean} enabled
* @param {object} options
* @param {boolean} [options.nodeModules]
* @param {boolean} [options.generatedCode]
*/
function setSourceMapsSupport(enabled, options = kEmptyObject) {
validateBoolean(enabled, 'enabled');
validateObject(options, 'options');
const { nodeModules = false, generatedCode = false } = options;
validateBoolean(nodeModules, 'options.nodeModules');
validateBoolean(generatedCode, 'options.generatedCode');
setSourceMapsNative(enabled);
if (enabled) {
const {
prepareStackTraceWithSourceMaps,
} = require('internal/source_map/prepare_stack_trace');
setInternalPrepareStackTrace(prepareStackTraceWithSourceMaps);
} else {
setInternalPrepareStackTrace(defaultPrepareStackTrace);
}
sourceMapsSupport = ObjectFreeze({
__proto__: null,
enabled,
nodeModules: nodeModules,
generatedCode: generatedCode,
});
}
/**
* Extracts the source url from the content if present. For example
* //# sourceURL=file:///path/to/file
*
* Read more at: https://tc39.es/source-map-spec/#linking-evald-code-to-named-generated-code
* @param {string} content - source content
* @returns {string | null} source url or null if not present
*/
function extractSourceURLMagicComment(content) {
let match;
let matchSourceURL;
// A while loop is used here to get the last occurrence of sourceURL.
// This is needed so that we don't match sourceURL in string literals.
while ((match = RegExpPrototypeExec(kSourceURLMagicComment, content))) {
matchSourceURL = match;
}
if (matchSourceURL == null) {
return null;
}
let sourceURL = matchSourceURL.groups.sourceURL;
if (sourceURL != null && RegExpPrototypeExec(kLeadingProtocol, sourceURL) === null) {
sourceURL = pathToFileURL(sourceURL).href;
}
return sourceURL;
}
/**
* Extracts the source map url from the content if present. For example
* //# sourceMappingURL=file:///path/to/file
*
* Read more at: https://tc39.es/source-map-spec/#linking-generated-code
* @param {string} content - source content
* @returns {string | null} source map url or null if not present
*/
function extractSourceMapURLMagicComment(content) {
let match;
let lastMatch;
// A while loop is used here to get the last occurrence of sourceMappingURL.
// This is needed so that we don't match sourceMappingURL in string literals.
while ((match = RegExpPrototypeExec(kSourceMappingURLMagicComment, content))) {
lastMatch = match;
}
if (lastMatch == null) {
return null;
}
return lastMatch.groups.sourceMappingURL;
}
/**
* Caches the source map, with the given filename, moduleInstance, sourceURL and sourceMapURL.
* This function does not automatically extract the source map from the content. The caller should either
* extract the source map from the content via V8 API or use {@link extractSourceURLMagicComment} explicitly.
* @param {string} filename - the actual filename
* @param {string} content - the actual source content
* @param {import('internal/modules/cjs/loader').Module | ModuleWrap} moduleInstance - a module instance that
* associated with the source, once this is reclaimed, the source map entry will be removed from the cache
* @param {boolean} isGeneratedSource - if the source was generated and evaluated with the global eval
* @param {string | undefined} sourceURL - the source url
* @param {string | undefined} sourceMapURL - the source map url
*/
function maybeCacheSourceMap(filename, content, moduleInstance, isGeneratedSource, sourceURL, sourceMapURL) {
const support = getSourceMapsSupport();
if (!(process.env.NODE_V8_COVERAGE || support.enabled)) return;
const { normalizeReferrerURL } = require('internal/modules/helpers');
filename = normalizeReferrerURL(filename);
if (filename === undefined) {
// This is most likely an invalid filename in sourceURL of [eval]-wrapper.
return;
}
if (!support.nodeModules && isUnderNodeModules(filename)) {
// Skip file under node_modules if not enabled.
return;
}
// Bail out when there is no source map url.
if (typeof sourceMapURL !== 'string') {
return;
}
// Normalize the sourceURL to a file URL if it is a path.
sourceURL = normalizeReferrerURL(sourceURL);
const data = dataFromUrl(filename, sourceMapURL);
// `data` could be null if the source map is invalid.
// In this case, create a cache entry with null data with source url for test coverage.
const entry = {
__proto__: null,
lineLengths: lineLengths(content),
data,
// Save the source map url if it is not a data url.
sourceMapURL: data ? null : sourceMapURL,
sourceURL,
};
if (isGeneratedSource) {
generatedSourceMapCache.set(filename, entry);
return;
}
// If it is not a generated source, we assume we are in a "cjs/esm"
// context.
const keys = sourceURL ? [filename, sourceURL] : [filename];
getModuleSourceMapCache().set(keys, entry, moduleInstance);
}
/**
* Caches the source map if it is present in the eval'd source.
* @param {string} content - the eval'd source code
*/
function maybeCacheGeneratedSourceMap(content) {
const support = getSourceMapsSupport();
if (!(process.env.NODE_V8_COVERAGE || support.enabled || support.generated)) return;
const sourceURL = extractSourceURLMagicComment(content);
if (sourceURL === null) {
return;
}
const sourceMapURL = extractSourceMapURLMagicComment(content);
if (sourceMapURL === null) {
return;
}
try {
// Use the sourceURL as the filename, and do not create a duplicate entry.
maybeCacheSourceMap(sourceURL, content, null, true, undefined /** no duplicated sourceURL */, sourceMapURL);
} catch (err) {
// This can happen if the filename is not a valid URL.
// If we fail to cache the source map, we should not fail the whole process.
debug(err);
}
}
/**
* Resolves source map payload data from the source url and source map url.
* If the source map url is a data url, the data is returned.
* Otherwise the source map url is resolved to a file path and the file is read.
* @param {string} sourceURL - url of the source file
* @param {string} sourceMappingURL - url of the source map
* @returns {object} deserialized source map JSON object
*/
function dataFromUrl(sourceURL, sourceMappingURL) {
const url = URLParse(sourceMappingURL);
if (url != null) {
switch (url.protocol) {
case 'data:':
return sourceMapFromDataUrl(sourceURL, url.pathname);
default:
debug(`unknown protocol ${url.protocol}`);
return null;
}
}
const mapURL = new URL(sourceMappingURL, sourceURL).href;
return sourceMapFromFile(mapURL);
}
// Cache the length of each line in the file that a source map was extracted
// from. This allows translation from byte offset V8 coverage reports,
// to line/column offset Source Map V3.
function lineLengths(content) {
const contentLength = content.length;
const output = [];
let lineLength = 0;
for (let i = 0; i < contentLength; i++, lineLength++) {
const codePoint = StringPrototypeCodePointAt(content, i);
// We purposefully keep \r as part of the line-length calculation, in
// cases where there is a \r\n separator, so that this can be taken into
// account in coverage calculations.
// codepoints for \n (new line), \u2028 (line separator) and \u2029 (paragraph separator)
if (codePoint === 10 || codePoint === 0x2028 || codePoint === 0x2029) {
ArrayPrototypePush(output, lineLength);
lineLength = -1; // To not count the matched codePoint such as \n character
}
}
ArrayPrototypePush(output, lineLength);
return output;
}
/**
* Read source map from file.
* @param {string} mapURL - file url of the source map
* @returns {object} deserialized source map JSON object
*/
function sourceMapFromFile(mapURL) {
try {
const fs = require('fs');
const content = fs.readFileSync(fileURLToPath(mapURL), 'utf8');
const data = JSONParse(content);
return sourcesToAbsolute(mapURL, data);
} catch (err) {
debug(err);
return null;
}
}
// data:[<mediatype>][;base64],<data> see:
// https://tools.ietf.org/html/rfc2397#section-2
function sourceMapFromDataUrl(sourceURL, url) {
const { 0: format, 1: data } = StringPrototypeSplit(url, ',', 2);
const splitFormat = StringPrototypeSplit(format, ';');
const contentType = splitFormat[0];
const base64 = splitFormat[splitFormat.length - 1] === 'base64';
if (contentType === 'application/json') {
const decodedData = base64 ?
Buffer.from(data, 'base64').toString('utf8') : data;
try {
const parsedData = JSONParse(decodedData);
return sourcesToAbsolute(sourceURL, parsedData);
} catch (err) {
// TODO(legendecas): warn about invalid source map JSON string.
// But it could be verbose.
debug(err);
return null;
}
} else {
debug(`unknown content-type ${contentType}`);
return null;
}
}
// If the sources are not absolute URLs after prepending of the "sourceRoot",
// the sources are resolved relative to the SourceMap (like resolving script
// src in a html document).
// If the sources are absolute paths, the sources are converted to absolute file URLs.
function sourcesToAbsolute(baseURL, data) {
data.sources = data.sources.map((source) => {
source = (data.sourceRoot || '') + source;
if (isAbsolute(source)) {
return pathToFileURL(source).href;
}
return new URL(source, baseURL).href;
});
// The sources array is now resolved to absolute URLs, sourceRoot should
// be updated to noop.
data.sourceRoot = '';
return data;
}
// WARNING: The `sourceMapCacheToObject` runs during shutdown. In particular,
// it also runs when Workers are terminated, making it important that it does
// not call out to any user-provided code, including built-in prototypes that
// might have been tampered with.
// Get serialized representation of source-map cache, this is used
// to persist a cache of source-maps to disk when NODE_V8_COVERAGE is enabled.
function sourceMapCacheToObject() {
const moduleSourceMapCache = getModuleSourceMapCache();
if (moduleSourceMapCache.size === 0) {
return undefined;
}
const obj = { __proto__: null };
for (const { 0: k, 1: v } of moduleSourceMapCache) {
obj[k] = {
__proto__: null,
lineLengths: v.lineLengths,
data: v.data,
url: v.sourceMapURL,
};
}
return obj;
}
/**
* Find a source map for a given actual source URL or path.
*
* This function may be invoked from user code or test runner, this must not throw
* any exceptions.
* @param {string} sourceURL - actual source URL or path
* @returns {import('internal/source_map/source_map').SourceMap | undefined} a source map or undefined if not found
*/
function findSourceMap(sourceURL) {
if (typeof sourceURL !== 'string') {
return undefined;
}
// No source maps for builtin modules.
if (sourceURL.startsWith('node:')) {
return undefined;
}
if (!getSourceMapsSupport().nodeModules && isUnderNodeModules(sourceURL)) {
return undefined;
}
SourceMap ??= require('internal/source_map/source_map').SourceMap;
try {
if (RegExpPrototypeExec(kLeadingProtocol, sourceURL) === null) {
// If the sourceURL is an invalid path, this will throw an error.
sourceURL = pathToFileURL(sourceURL).href;
}
const entry = getModuleSourceMapCache().get(sourceURL) ?? generatedSourceMapCache.get(sourceURL);
if (entry?.data == null) {
return undefined;
}
let sourceMap = entry.sourceMap;
if (sourceMap === undefined) {
sourceMap = new SourceMap(entry.data, { lineLengths: entry.lineLengths });
entry.sourceMap = sourceMap;
}
return sourceMap;
} catch (err) {
debug(err);
return undefined;
}
}
module.exports = {
findSourceMap,
getSourceMapsSupport,
setSourceMapsSupport,
maybeCacheSourceMap,
maybeCacheGeneratedSourceMap,
sourceMapCacheToObject,
};

View File

@ -0,0 +1,115 @@
'use strict';
const {
ArrayPrototypeForEach,
ObjectFreeze,
SafeFinalizationRegistry,
SafeMap,
SafeWeakRef,
SymbolIterator,
} = primordials;
const {
privateSymbols: {
source_map_data_private_symbol,
},
} = internalBinding('util');
/**
* Specialized map of WeakRefs to module instances that caches source map
* entries by `filename` and `sourceURL`. Cached entries can be iterated with
* `for..of` syntax.
*
* The cache map maintains the cache entries by:
* - `weakModuleMap`(Map): a strong sourceURL -> WeakRef(Module),
* - WeakRef(Module[source_map_data_private_symbol]): source map data.
*
* Obsolete `weakModuleMap` entries are removed by the `finalizationRegistry`
* callback. This pattern decouples the strong url reference to the source map
* data and allow the cache to be reclaimed eagerly, without depending on an
* indeterministic callback of a finalization registry.
*/
class SourceMapCacheMap {
/**
* @type {Map<string, WeakRef<*>>}
* The cached module instance can be removed from the global module registry
* with approaches like mutating `require.cache`.
* The `weakModuleMap` exposes entries by `filename` and `sourceURL`.
* In the case of mutated module registry, obsolete entries are removed from
* the cache by the `finalizationRegistry`.
*/
#weakModuleMap = new SafeMap();
#cleanup = ({ keys }) => {
// Delete the entry if the weak target has been reclaimed.
// If the weak target is not reclaimed, the entry was overridden by a new
// weak target.
ArrayPrototypeForEach(keys, (key) => {
const ref = this.#weakModuleMap.get(key);
if (ref && ref.deref() === undefined) {
this.#weakModuleMap.delete(key);
}
});
};
#finalizationRegistry = new SafeFinalizationRegistry(this.#cleanup);
/**
* Sets the value for the given key, associated with the given module
* instance.
* @param {string[]} keys array of urls to index the value entry.
* @param {*} sourceMapData the value entry.
* @param {object} moduleInstance an object that can be weakly referenced and
* invalidate the [key, value] entry after this object is reclaimed.
*/
set(keys, sourceMapData, moduleInstance) {
const weakRef = new SafeWeakRef(moduleInstance);
ArrayPrototypeForEach(keys, (key) => this.#weakModuleMap.set(key, weakRef));
moduleInstance[source_map_data_private_symbol] = sourceMapData;
this.#finalizationRegistry.register(moduleInstance, { keys });
}
/**
* Get an entry by the given key.
* @param {string} key a file url or source url
*/
get(key) {
const weakRef = this.#weakModuleMap.get(key);
const moduleInstance = weakRef?.deref();
if (moduleInstance === undefined) {
return;
}
return moduleInstance[source_map_data_private_symbol];
}
/**
* Estimate the size of the cache. The actual size may be smaller because
* some entries may be reclaimed with the module instance.
*/
get size() {
return this.#weakModuleMap.size;
}
[SymbolIterator]() {
const iterator = this.#weakModuleMap.entries();
const next = () => {
const result = iterator.next();
if (result.done) return result;
const { 0: key, 1: weakRef } = result.value;
const moduleInstance = weakRef.deref();
if (moduleInstance == null) return next();
const value = moduleInstance[source_map_data_private_symbol];
return { done: false, value: [key, value] };
};
return {
[SymbolIterator]() { return this; },
next,
};
}
}
ObjectFreeze(SourceMapCacheMap.prototype);
module.exports = {
SourceMapCacheMap,
};