mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-30 09:54:02 +08:00
Bug: 1299785 Change-Id: Ie05a084245ab8218e7f803e46403a935e109a203 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/3629868 Commit-Queue: Kateryna Prokopenko <kprokopenko@chromium.org> Reviewed-by: Simon Zünd <szuend@chromium.org> Reviewed-by: Eric Leese <leese@chromium.org>
155 lines
5.5 KiB
TypeScript
155 lines
5.5 KiB
TypeScript
// Copyright 2022 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import * as Common from '../../core/common/common.js';
|
|
import type * as Platform from '../../core/platform/platform.js';
|
|
import type * as SDK from '../../core/sdk/sdk.js';
|
|
import type * as Protocol from '../../generated/protocol.js';
|
|
|
|
export interface ParsedErrorFrame {
|
|
line: string;
|
|
link?: {
|
|
url: Platform.DevToolsPath.UrlString,
|
|
prefix: string,
|
|
suffix: string,
|
|
lineNumber?: number,
|
|
columnNumber?: number, enclosedInBraces: boolean,
|
|
scriptId?: Protocol.Runtime.ScriptId,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Takes a V8 Error#stack string and extracts source position information.
|
|
*
|
|
* The result includes the url, line and column number, as well as where
|
|
* the url is found in the raw line.
|
|
*
|
|
* @returns Null if the provided string has an unexpected format. A
|
|
* populated `ParsedErrorFrame[]` otherwise.
|
|
*/
|
|
export function parseSourcePositionsFromErrorStack(
|
|
runtimeModel: SDK.RuntimeModel.RuntimeModel, stack: string): ParsedErrorFrame[]|null {
|
|
if (!/^[\w.]*Error\b/.test(stack)) {
|
|
return null;
|
|
}
|
|
const debuggerModel = runtimeModel.debuggerModel();
|
|
const baseURL = runtimeModel.target().inspectedURL();
|
|
|
|
const lines = stack.split('\n');
|
|
const linkInfos = [];
|
|
for (const line of lines) {
|
|
const isCallFrameLine = /^\s*at\s/.test(line);
|
|
if (!isCallFrameLine && linkInfos.length && linkInfos[linkInfos.length - 1].link) {
|
|
return null;
|
|
}
|
|
|
|
if (!isCallFrameLine) {
|
|
linkInfos.push({line});
|
|
continue;
|
|
}
|
|
|
|
let openBracketIndex = -1;
|
|
let closeBracketIndex = -1;
|
|
const inBracketsWithLineAndColumn = /\([^\)\(]+:\d+:\d+\)/g;
|
|
const inBrackets = /\([^\)\(]+\)/g;
|
|
let lastMatch: RegExpExecArray|null = null;
|
|
let currentMatch;
|
|
while ((currentMatch = inBracketsWithLineAndColumn.exec(line))) {
|
|
lastMatch = currentMatch;
|
|
}
|
|
if (!lastMatch) {
|
|
while ((currentMatch = inBrackets.exec(line))) {
|
|
lastMatch = currentMatch;
|
|
}
|
|
}
|
|
if (lastMatch) {
|
|
openBracketIndex = lastMatch.index;
|
|
closeBracketIndex = lastMatch.index + lastMatch[0].length - 1;
|
|
}
|
|
const hasOpenBracket = openBracketIndex !== -1;
|
|
let left = hasOpenBracket ? openBracketIndex + 1 : line.indexOf('at') + 3;
|
|
if (!hasOpenBracket && line.indexOf('async ') === left) {
|
|
left += 6;
|
|
}
|
|
const right = hasOpenBracket ? closeBracketIndex : line.length;
|
|
const linkCandidate = line.substring(left, right);
|
|
const splitResult = Common.ParsedURL.ParsedURL.splitLineAndColumn(linkCandidate);
|
|
if (splitResult.url === '<anonymous>') {
|
|
linkInfos.push({line});
|
|
continue;
|
|
}
|
|
let url = parseOrScriptMatch(debuggerModel, splitResult.url);
|
|
if (!url && Common.ParsedURL.ParsedURL.isRelativeURL(splitResult.url)) {
|
|
url = parseOrScriptMatch(debuggerModel, Common.ParsedURL.ParsedURL.completeURL(baseURL, splitResult.url));
|
|
}
|
|
if (!url) {
|
|
return null;
|
|
}
|
|
|
|
linkInfos.push({
|
|
line,
|
|
link: {
|
|
url,
|
|
prefix: line.substring(0, left),
|
|
suffix: line.substring(right),
|
|
enclosedInBraces: hasOpenBracket,
|
|
lineNumber: splitResult.lineNumber,
|
|
columnNumber: splitResult.columnNumber,
|
|
},
|
|
});
|
|
}
|
|
return linkInfos;
|
|
}
|
|
|
|
function parseOrScriptMatch(debuggerModel: SDK.DebuggerModel.DebuggerModel, url: Platform.DevToolsPath.UrlString|null):
|
|
Platform.DevToolsPath.UrlString|null {
|
|
if (!url) {
|
|
return null;
|
|
}
|
|
if (Common.ParsedURL.ParsedURL.isValidUrlString(url)) {
|
|
return url;
|
|
}
|
|
if (debuggerModel.scriptsForSourceURL(url).length) {
|
|
return url;
|
|
}
|
|
// nodejs stack traces contain (absolute) file paths, but v8 reports them as file: urls.
|
|
const fileUrl = new URL(url, 'file://');
|
|
if (debuggerModel.scriptsForSourceURL(fileUrl.href).length) {
|
|
return fileUrl.href as Platform.DevToolsPath.UrlString;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Error#stack output only contains script URLs. In some cases we are able to
|
|
* retrieve additional exception details from V8 that we can use to augment
|
|
* the parsed Error#stack with script IDs.
|
|
* This function sets the `scriptId` field in `ParsedErrorFrame` when it finds
|
|
* the corresponding info in `Protocol.Runtime.StackTrace`.
|
|
*/
|
|
export function augmentErrorStackWithScriptIds(
|
|
parsedFrames: ParsedErrorFrame[], protocolStackTrace: Protocol.Runtime.StackTrace): void {
|
|
// Note that the number of frames between the two stack traces can differ. The
|
|
// parsed Error#stack can contain Builtin frames which are not present in the protocol
|
|
// stack. This means its easier to always search the whole protocol stack for a matching
|
|
// frame rather then trying to detect the Builtin frames and skipping them.
|
|
for (const parsedFrame of parsedFrames) {
|
|
const protocolFrame = protocolStackTrace.callFrames.find(frame => framesMatch(parsedFrame, frame));
|
|
if (protocolFrame && parsedFrame.link) {
|
|
parsedFrame.link.scriptId = protocolFrame.scriptId;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Returns true iff both stack frames have the same url and line/column numbers. The function name is ignored */
|
|
function framesMatch(parsedFrame: ParsedErrorFrame, protocolFrame: Protocol.Runtime.CallFrame): boolean {
|
|
if (!parsedFrame.link) {
|
|
return false;
|
|
}
|
|
|
|
const {url, lineNumber, columnNumber} = parsedFrame.link;
|
|
return url === protocolFrame.url && lineNumber === protocolFrame.lineNumber &&
|
|
columnNumber === protocolFrame.columnNumber;
|
|
}
|