-
Notifications
You must be signed in to change notification settings - Fork 48.7k
[DevTools] Get source location from structured callsites in prepareStackTrace #33143
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -18,6 +18,8 @@ import type {DehydratedData} from 'react-devtools-shared/src/frontend/types'; | |||||
export {default as formatWithStyles} from './formatWithStyles'; | ||||||
export {default as formatConsoleArguments} from './formatConsoleArguments'; | ||||||
|
||||||
import {formatOwnerStackString} from '../shared/DevToolsOwnerStack'; | ||||||
|
||||||
// TODO: update this to the first React version that has a corresponding DevTools backend | ||||||
const FIRST_DEVTOOLS_BACKEND_LOCKSTEP_VER = '999.9.9'; | ||||||
export function hasAssignedBackend(version?: string): boolean { | ||||||
|
@@ -345,6 +347,77 @@ export function parseSourceFromComponentStack( | |||||
return parseSourceFromFirefoxStack(componentStack); | ||||||
} | ||||||
|
||||||
let collectedLocation: Source | null = null; | ||||||
|
||||||
function collectStackTrace( | ||||||
error: Error, | ||||||
structuredStackTrace: CallSite[], | ||||||
): string { | ||||||
Comment on lines
+352
to
+355
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe worth leaving a link to V8 docs for this - https://v8.dev/docs/stack-trace-api#customizing-stack-traces. |
||||||
let result: null | Source = null; | ||||||
// Collect structured stack traces from the callsites. | ||||||
// We mirror how V8 serializes stack frames and how we later parse them. | ||||||
for (let i = 0; i < structuredStackTrace.length; i++) { | ||||||
const callSite = structuredStackTrace[i]; | ||||||
if (callSite.getFunctionName() === 'react-stack-bottom-frame') { | ||||||
// We pick the last frame that matches before the bottom frame since | ||||||
// that will be immediately inside the component as opposed to some helper. | ||||||
// If we don't find a bottom frame then we bail to string parsing. | ||||||
collectedLocation = result; | ||||||
// Skip everything after the bottom frame since it'll be internals. | ||||||
break; | ||||||
} else { | ||||||
const sourceURL = callSite.getScriptNameOrSourceURL(); | ||||||
const line = | ||||||
// $FlowFixMe[prop-missing] | ||||||
typeof callSite.getEnclosingLineNumber === 'function' | ||||||
? (callSite: any).getEnclosingLineNumber() | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the key. Now we're jumping to the beginning of the function. |
||||||
: callSite.getLineNumber(); | ||||||
const col = | ||||||
// $FlowFixMe[prop-missing] | ||||||
typeof callSite.getEnclosingColumnNumber === 'function' | ||||||
? (callSite: any).getEnclosingColumnNumber() | ||||||
: callSite.getLineNumber(); | ||||||
if (!sourceURL || !line || !col) { | ||||||
// Skip eval etc. without source url. They don't have location. | ||||||
continue; | ||||||
} | ||||||
result = { | ||||||
sourceURL, | ||||||
line: line, | ||||||
column: col, | ||||||
}; | ||||||
} | ||||||
} | ||||||
// At the same time we generate a string stack trace just in case someone | ||||||
// else reads it. | ||||||
const name = error.name || 'Error'; | ||||||
const message = error.message || ''; | ||||||
let stack = name + ': ' + message; | ||||||
for (let i = 0; i < structuredStackTrace.length; i++) { | ||||||
stack += '\n at ' + structuredStackTrace[i].toString(); | ||||||
} | ||||||
return stack; | ||||||
Comment on lines
+391
to
+399
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't get how this can be read? You patched |
||||||
} | ||||||
|
||||||
export function parseSourceFromOwnerStack(error: Error): Source | null { | ||||||
// First attempt to collected the structured data using prepareStackTrace. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
collectedLocation = null; | ||||||
const previousPrepare = Error.prepareStackTrace; | ||||||
Error.prepareStackTrace = collectStackTrace; | ||||||
let stack; | ||||||
try { | ||||||
stack = error.stack; | ||||||
} finally { | ||||||
Error.prepareStackTrace = previousPrepare; | ||||||
} | ||||||
if (collectedLocation !== null) { | ||||||
return collectedLocation; | ||||||
} | ||||||
// Fallback to parsing the string form. | ||||||
const componentStack = formatOwnerStackString(stack); | ||||||
return parseSourceFromComponentStack(componentStack); | ||||||
} | ||||||
|
||||||
// 0.123456789 => 0.123 | ||||||
// Expects high-resolution timestamp in milliseconds, like from performance.now() | ||||||
// Mainly used for optimizing the size of serialized profiling payload | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you would make
isError
a type guard, will it be enough to remove theany
type casting?