"use strict"; Object.defineProperty(exports, "__esModule", { value: true, }); Object.defineProperty(exports, "SourceMetadataMapConsumer", { enumerable: true, get: function () { return _SourceMetadataMapConsumer.default; }, }); exports.createContext = createContext; exports.getOriginalPositionFor = getOriginalPositionFor; exports.parseFileName = parseSingleMapFileName; exports.symbolicate = symbolicate; exports.symbolicateAttribution = symbolicateAttribution; exports.symbolicateChromeTrace = symbolicateChromeTrace; exports.symbolicateProfilerMap = symbolicateProfilerMap; exports.unstable_createDirectoryContext = unstable_createDirectoryContext; var _ChromeHeapSnapshot = require("./ChromeHeapSnapshot"); var _GoogleIgnoreListConsumer = _interopRequireDefault( require("./GoogleIgnoreListConsumer"), ); var _SourceMetadataMapConsumer = _interopRequireDefault( require("./SourceMetadataMapConsumer"), ); var _fs = _interopRequireDefault(require("fs")); var _invariant = _interopRequireDefault(require("invariant")); var _nullthrows = _interopRequireDefault(require("nullthrows")); var _path = _interopRequireDefault(require("path")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } const UNKNOWN_MODULE_IDS = { segmentId: 0, localId: undefined, }; class SymbolicationContext { constructor(options) { this.options = { inputLineStart: 1, inputColumnStart: 0, outputLineStart: 1, outputColumnStart: 0, nameSource: "function_names", }; if (options) { for (const option of [ "inputLineStart", "inputColumnStart", "outputLineStart", "outputColumnStart", ]) { if (options[option] != null) { this.options[option] = options[option]; } } if (options.nameSource != null) { this.options.nameSource = options.nameSource; } } } symbolicate(stackTrace) { return stackTrace.replace( /(?:([^@: \n(]+)(@|:))?(?:(?:([^@: \n(]+):)?(\d+):(\d+)|\[native code\])/g, (match, func, delimiter, fileName, line, column) => { if (delimiter === ":" && func && !fileName) { fileName = func; func = null; } const original = this.getOriginalPositionFor( line, column, this.parseFileName(fileName || ""), ); return ( (original.source ?? "null") + ":" + (original.line ?? "null") + ":" + (original.name ?? "null") ); }, ); } symbolicateProfilerMap(mapFile) { return _fs.default .readFileSync(mapFile, "utf8") .split("\n") .slice(0, -1) .map((line) => { const line_list = line.split(" "); const trampoline = line_list[0]; const js_name = line_list[1]; const offset = parseInt(line_list[2], 10); if (!offset) { return trampoline + " " + trampoline; } const original = this.getOriginalPositionFor( this.options.inputLineStart, offset, ); return ( trampoline + " " + (original.name || js_name) + "::" + [original.source, original.line, original.column].join(":") ); }) .join("\n"); } symbolicateAttribution(obj) { const loc = obj.location; const line = loc.line != null ? loc.line : this.options.inputLineStart; let column = Number(loc.column != null ? loc.column : loc.virtualOffset); const file = loc.filename ? this.parseFileName(loc.filename) : null; let original = this.getOriginalPositionFor(line, column, file); const isBytecodeRange = loc.bytecodeSize != null && loc.virtualOffset != null && loc.column == null; const virtualOffset = Number(loc.virtualOffset); const bytecodeSize = Number(loc.bytecodeSize); while ( isBytecodeRange && original.source == null && ++column < virtualOffset + bytecodeSize ) { original = this.getOriginalPositionFor(line, column, file); } obj.location = { file: original.source, line: original.line, column: original.column, }; } symbolicateChromeTrace(traceFile, { stdout, stderr }) { const content = JSON.parse(_fs.default.readFileSync(traceFile, "utf8")); if (content.stackFrames == null) { throw new Error("Unable to locate `stackFrames` section in trace."); } const keys = Object.keys(content.stackFrames); stdout.write("Processing " + keys.length + " frames\n"); keys.forEach((key) => { const entry = content.stackFrames[key]; let line; let column; let funcLine; let funcColumn; if (entry.funcVirtAddr != null && entry.offset != null) { const funcVirtAddr = parseInt(entry.funcVirtAddr, 10); const offsetInFunction = parseInt(entry.offset, 10); line = this.options.inputLineStart; column = funcVirtAddr + offsetInFunction; funcLine = this.options.inputLineStart; funcColumn = funcVirtAddr; } else if (entry.line != null && entry.column != null) { line = entry.line; column = entry.column; funcLine = entry.funcLine; funcColumn = entry.funcColumn; } else { return; } const addressOriginal = this.getOriginalPositionDetailsFor(line, column); let frameName; if (addressOriginal.functionName) { frameName = addressOriginal.functionName; } else { frameName = entry.name; if (funcLine != null && funcColumn != null) { const funcOriginal = this.getOriginalPositionFor( funcLine, funcColumn, ); if (funcOriginal.name != null) { frameName = funcOriginal.name; } } else { (stderr || stdout).write( "Warning: no function prolog line/column info; name may be wrong\n", ); } } entry.name = [ frameName, "(", [ addressOriginal.source ?? "null", addressOriginal.line ?? "null", addressOriginal.column ?? "null", ].join(":"), ")", ].join(""); }); stdout.write("Writing to " + traceFile + "\n"); _fs.default.writeFileSync(traceFile, JSON.stringify(content)); } getOriginalPositionFor(lineNumber, columnNumber, moduleIds) { const position = this.getOriginalPositionDetailsFor( lineNumber, columnNumber, moduleIds, ); return { line: position.line, column: position.column, source: position.source, name: position.functionName ? position.functionName : position.name, }; } symbolicateHermesMinidumpTrace(crashInfo) { throw new Error("Not implemented"); } symbolicateHeapSnapshot(snapshotContents) { const snapshotData = typeof snapshotContents === "string" ? JSON.parse(snapshotContents) : snapshotContents; const processor = new _ChromeHeapSnapshot.ChromeHeapSnapshotProcessor( snapshotData, ); for (const frame of processor.traceFunctionInfos()) { const moduleIds = this.parseFileName(frame.getString("script_name")); const generatedLine = frame.getNumber("line"); const generatedColumn = frame.getNumber("column"); if (generatedLine === 0 && generatedColumn === 0) { continue; } const { line: originalLine, column: originalColumn, source: originalSource, functionName: originalFunctionName, } = this.getOriginalPositionDetailsFor( frame.getNumber("line") - 1 + this.options.inputLineStart, frame.getNumber("column") - 1 + this.options.inputColumnStart, moduleIds, ); if (originalSource != null) { frame.setString("script_name", originalSource); if (originalLine != null) { frame.setNumber( "line", originalLine - this.options.outputLineStart + 1, ); } else { frame.setNumber("line", 0); } if (originalColumn != null) { frame.setNumber( "column", originalColumn - this.options.outputColumnStart + 1, ); } else { frame.setNumber("column", 0); } } frame.setString("name", originalFunctionName ?? frame.getString("name")); } return snapshotData; } symbolicateHermesCoverageTrace(coverageInfo) { const symbolicatedTrace = []; const { executedFunctions } = coverageInfo; if (executedFunctions != null) { for (const stackItem of executedFunctions) { const { line, column, SourceURL } = stackItem; const generatedLine = line + this.options.inputLineStart; const generatedColumn = column + this.options.inputColumnStart; const originalPosition = this.getOriginalPositionDetailsFor( generatedLine, generatedColumn, this.parseFileName(SourceURL || ""), ); symbolicatedTrace.push(originalPosition); } } return symbolicatedTrace; } getOriginalPositionDetailsFor(lineNumber, columnNumber, moduleIds) { throw new Error("Not implemented"); } parseFileName(str) { throw new Error("Not implemented"); } } class SingleMapSymbolicationContext extends SymbolicationContext { constructor(SourceMapConsumer, sourceMapContent, options = {}) { super(options); this._SourceMapConsumer = SourceMapConsumer; const sourceMapJson = typeof sourceMapContent === "string" ? JSON.parse(sourceMapContent.replace(/^\)\]\}'/, "")) : sourceMapContent; const segments = { 0: this._initSegment(sourceMapJson), }; if (sourceMapJson.x_facebook_segments) { for (const key of Object.keys(sourceMapJson.x_facebook_segments)) { const map = sourceMapJson.x_facebook_segments[key]; segments[key] = this._initSegment(map); } } this._legacyFormat = sourceMapJson.x_facebook_segments != null || sourceMapJson.x_facebook_offsets != null; this._segments = segments; } _initSegment(map) { const useFunctionNames = this.options.nameSource === "function_names"; const { _SourceMapConsumer: SourceMapConsumer } = this; return { get consumer() { Object.defineProperty(this, "consumer", { value: new SourceMapConsumer(map), }); return this.consumer; }, moduleOffsets: map.x_facebook_offsets || [], get sourceFunctionsConsumer() { Object.defineProperty(this, "sourceFunctionsConsumer", { value: useFunctionNames ? new _SourceMetadataMapConsumer.default(map) : null, }); return this.sourceFunctionsConsumer; }, get googleIgnoreListConsumer() { Object.defineProperty(this, "googleIgnoreListConsumer", { value: new _GoogleIgnoreListConsumer.default(map), }); return this.googleIgnoreListConsumer; }, hermesOffsets: map.x_hermes_function_offsets, }; } symbolicateHermesMinidumpTrace(crashInfo) { const symbolicatedTrace = []; const { callstack } = crashInfo; if (callstack != null) { for (const stackItem of callstack) { if (stackItem.NativeCode) { symbolicatedTrace.push(stackItem); } else { const { CJSModuleOffset, SegmentID, SourceURL, FunctionID, ByteCodeOffset: localOffset, } = stackItem; const cjsModuleOffsetOrSegmentID = (0, _nullthrows.default)( CJSModuleOffset ?? SegmentID, "Either CJSModuleOffset or SegmentID must be specified in the Hermes stack frame", ); const moduleInformation = this.parseFileName(SourceURL); const generatedLine = cjsModuleOffsetOrSegmentID + this.options.inputLineStart; const segment = this._segments[moduleInformation.segmentId.toString()]; const hermesOffsets = segment?.hermesOffsets; if (!hermesOffsets) { symbolicatedTrace.push({ line: null, column: null, source: null, functionName: null, name: null, isIgnored: false, }); } else { const segmentOffsets = hermesOffsets[Number(cjsModuleOffsetOrSegmentID)]; const generatedColumn = segmentOffsets[FunctionID] + localOffset + this.options.inputColumnStart; const originalPosition = this.getOriginalPositionDetailsFor( generatedLine, generatedColumn, moduleInformation, ); symbolicatedTrace.push(originalPosition); } } } } return symbolicatedTrace; } symbolicateHermesCoverageTrace(coverageInfo) { const symbolicatedTrace = []; const { executedFunctions } = coverageInfo; if (executedFunctions != null) { for (const stackItem of executedFunctions) { const { line, column, SourceURL } = stackItem; const generatedLine = line + this.options.inputLineStart; const generatedColumn = column + this.options.inputColumnStart; const originalPosition = this.getOriginalPositionDetailsFor( generatedLine, generatedColumn, this.parseFileName(SourceURL || ""), ); symbolicatedTrace.push(originalPosition); } } return symbolicatedTrace; } getOriginalPositionDetailsFor(lineNumber, columnNumber, moduleIds) { lineNumber = lineNumber != null ? lineNumber - this.options.inputLineStart + 1 : lineNumber; columnNumber = columnNumber != null ? columnNumber - this.options.inputColumnStart + 0 : columnNumber; if (!moduleIds) { moduleIds = UNKNOWN_MODULE_IDS; } let moduleLineOffset = 0; const metadata = this._segments[moduleIds.segmentId + ""]; const { localId } = moduleIds; if (localId != null) { const { moduleOffsets } = metadata; if (!moduleOffsets) { throw new Error( "Module ID given for a source map that does not have " + "an x_facebook_offsets field", ); } if (moduleOffsets[localId] == null) { throw new Error("Unknown module ID: " + localId); } moduleLineOffset = moduleOffsets[localId]; } const original = metadata.consumer.originalPositionFor({ line: Number(lineNumber) + moduleLineOffset, column: Number(columnNumber), }); if (metadata.sourceFunctionsConsumer) { original.functionName = metadata.sourceFunctionsConsumer.functionNameFor(original) || null; } else { original.functionName = null; } original.isIgnored = metadata.googleIgnoreListConsumer.isIgnored(original); return { ...original, line: original.line != null ? original.line - 1 + this.options.outputLineStart : original.line, column: original.column != null ? original.column - 0 + this.options.outputColumnStart : original.column, }; } parseFileName(str) { if (this._legacyFormat) { return parseSingleMapFileName(str); } return UNKNOWN_MODULE_IDS; } } class DirectorySymbolicationContext extends SymbolicationContext { constructor(SourceMapConsumer, rootDir, options = {}) { super(options); this._fileMaps = new Map(); this._rootDir = rootDir; this._SourceMapConsumer = SourceMapConsumer; } _loadMap(mapFilename) { (0, _invariant.default)( _fs.default.existsSync(mapFilename), `Could not read source map from '${mapFilename}'`, ); let fileMap = this._fileMaps.get(mapFilename); if (fileMap == null) { fileMap = new SingleMapSymbolicationContext( this._SourceMapConsumer, _fs.default.readFileSync(mapFilename, "utf8"), this.options, ); this._fileMaps.set(mapFilename, fileMap); } return fileMap; } getOriginalPositionDetailsFor(lineNumber, columnNumber, filename) { (0, _invariant.default)( filename != null, "filename is required for DirectorySymbolicationContext", ); let mapFilename; const relativeFilename = _path.default.relative( this._rootDir, _path.default.resolve(this._rootDir, filename), ); if (!relativeFilename.startsWith("..")) { mapFilename = _path.default.join( this._rootDir, relativeFilename + ".map", ); } if (mapFilename == null || !_fs.default.existsSync(mapFilename)) { lineNumber = lineNumber != null ? lineNumber - this.options.inputLineStart + this.options.outputLineStart : lineNumber; columnNumber = columnNumber != null ? columnNumber - this.options.inputColumnStart + this.options.outputColumnStart : columnNumber; return { line: lineNumber, column: columnNumber, source: filename, name: null, functionName: null, isIgnored: false, }; } return this._loadMap(mapFilename).getOriginalPositionDetailsFor( lineNumber, columnNumber, ); } parseFileName(str) { return str; } } function parseSingleMapFileName(str) { const modMatch = str.match(/^(\d+).js$/); if (modMatch != null) { return { segmentId: 0, localId: Number(modMatch[1]), }; } const segMatch = str.match(/^seg-(\d+)(?:_(\d+))?.js$/); if (segMatch != null) { return { segmentId: Number(segMatch[1]), localId: segMatch[2] ? Number(segMatch[2]) : null, }; } return UNKNOWN_MODULE_IDS; } function createContext(SourceMapConsumer, sourceMapContent, options = {}) { return new SingleMapSymbolicationContext( SourceMapConsumer, sourceMapContent, options, ); } function unstable_createDirectoryContext( SourceMapConsumer, rootDir, options = {}, ) { return new DirectorySymbolicationContext(SourceMapConsumer, rootDir, options); } function getOriginalPositionFor(lineNumber, columnNumber, moduleIds, context) { return context.getOriginalPositionFor(lineNumber, columnNumber, moduleIds); } function symbolicate(stackTrace, context) { return context.symbolicate(stackTrace); } function symbolicateProfilerMap(mapFile, context) { return context.symbolicateProfilerMap(mapFile); } function symbolicateAttribution(obj, context) { context.symbolicateAttribution(obj); } function symbolicateChromeTrace(traceFile, { stdout, stderr }, context) { return context.symbolicateChromeTrace(traceFile, { stdout, stderr, }); }