2
2
3
3
import { readFile } from "node:fs/promises" ;
4
4
import { parseSourceMapPath } from "./wasmparser.js" ;
5
+ import { BasicSourceMapConsumer , IndexedSourceMapConsumer , SourceMapConsumer } from "source-map" ;
5
6
6
7
export interface WebAssemblyCallSite {
7
8
functionName : string ;
@@ -12,7 +13,41 @@ export interface WebAssemblyCallSite {
12
13
13
14
interface WebAssemblyModuleInfo {
14
15
wasmPath : string ;
15
- sourceMapUrl : string | null ;
16
+ sourceMapConsumer : SourceMapHandler | null ;
17
+ }
18
+
19
+ type SourceMapHandler = BasicSourceMapConsumer | IndexedSourceMapConsumer ;
20
+
21
+ interface SourceLocation {
22
+ fileName : string ;
23
+ lineNumber : number ;
24
+ columnNumber : number ;
25
+ }
26
+
27
+ function getOriginLocationWithSourceMap (
28
+ line : number | null ,
29
+ column : number | null ,
30
+ sourceMapConsumer : SourceMapHandler | null
31
+ ) : SourceLocation | null {
32
+ if ( sourceMapConsumer === null || line === null || column === null ) {
33
+ return null ;
34
+ }
35
+ const originPosition = sourceMapConsumer . originalPositionFor ( {
36
+ line : line ,
37
+ column : column ,
38
+ } ) ;
39
+ if ( originPosition . source === null || originPosition . line === null || originPosition . column === null ) {
40
+ return null ;
41
+ }
42
+ return {
43
+ fileName : originPosition . source ,
44
+ lineNumber : originPosition . line ,
45
+ columnNumber : originPosition . column ,
46
+ } ;
47
+ }
48
+
49
+ function getWebAssemblyFunctionName ( callSite : NodeJS . CallSite ) : string {
50
+ return callSite . getFunctionName ( ) ?? `wasm-function[${ callSite . getFunction ( ) } ]` ;
16
51
}
17
52
18
53
function createWebAssemblyCallSite (
@@ -23,45 +58,65 @@ function createWebAssemblyCallSite(
23
58
// ignore non-wasm call sites
24
59
return null ;
25
60
}
26
- if ( moduleInfo . sourceMapUrl !== null ) {
27
- // use source map
61
+ const line : number | null = callSite . getLineNumber ( ) ;
62
+ const column : number | null = callSite . getColumnNumber ( ) ;
63
+ const originalPosition : SourceLocation | null = getOriginLocationWithSourceMap (
64
+ line ,
65
+ column ,
66
+ moduleInfo . sourceMapConsumer
67
+ ) ;
68
+ if ( originalPosition ) {
28
69
return {
29
- fileName : moduleInfo . wasmPath ,
30
- functionName : `wasm-function[ ${ callSite . getFunction ( ) } ]` ,
31
- lineNumber : callSite . getLineNumber ( ) || - 1 ,
32
- columnNumber : callSite . getColumnNumber ( ) || - 1 ,
70
+ fileName : originalPosition . fileName ,
71
+ functionName : getWebAssemblyFunctionName ( callSite ) ,
72
+ lineNumber : originalPosition . lineNumber ,
73
+ columnNumber : originalPosition . columnNumber ,
33
74
} ;
34
75
}
35
- // default
76
+ // fallback to the original call site
36
77
return {
37
78
fileName : moduleInfo . wasmPath ,
38
- functionName : `wasm-function[ ${ callSite . getFunction ( ) } ]` ,
39
- lineNumber : callSite . getLineNumber ( ) || - 1 ,
40
- columnNumber : callSite . getColumnNumber ( ) || - 1 ,
79
+ functionName : getWebAssemblyFunctionName ( callSite ) ,
80
+ lineNumber : line || - 1 ,
81
+ columnNumber : column || - 1 ,
41
82
} ;
42
83
}
43
84
44
- export class ExecutionError {
45
- constructor (
46
- public message : string ,
47
- public stacks : WebAssemblyCallSite [ ]
48
- ) { }
85
+ export interface ExecutionError {
86
+ message : string ;
87
+ stacks : WebAssemblyCallSite [ ] ;
88
+ }
89
+
90
+ async function getSourceMapConsumer ( sourceMapPath : string ) : Promise < BasicSourceMapConsumer | IndexedSourceMapConsumer > {
91
+ return new Promise < BasicSourceMapConsumer | IndexedSourceMapConsumer > ( async ( resolve , reject ) => {
92
+ let sourceMapContent : string | null = null ;
93
+ try {
94
+ sourceMapContent = await readFile ( sourceMapPath , "utf8" ) ;
95
+ } catch ( error ) {
96
+ reject ( error ) ;
97
+ return ;
98
+ }
99
+ SourceMapConsumer . with ( sourceMapContent , null , ( consumer ) => {
100
+ resolve ( consumer ) ;
101
+ } ) ;
102
+ } ) ;
49
103
}
50
104
51
105
export async function handleWebAssemblyError (
52
106
error : WebAssembly . RuntimeError ,
53
107
wasmPath : string
54
108
) : Promise < ExecutionError > {
55
109
const wasmBuffer = await readFile ( wasmPath ) ;
56
- const sourceMapUrl = parseSourceMapPath ( wasmBuffer . buffer as ArrayBuffer ) ;
110
+ const sourceMapPath = parseSourceMapPath ( wasmBuffer . buffer as ArrayBuffer ) ;
111
+ const sourceMapConsumer = sourceMapPath ? await getSourceMapConsumer ( sourceMapPath ) : null ;
57
112
const originalPrepareStackTrace = Error . prepareStackTrace ;
58
113
let stacks : WebAssemblyCallSite [ ] = [ ] ;
59
114
Error . prepareStackTrace = ( _ : Error , structuredStackTrace : NodeJS . CallSite [ ] ) => {
60
115
stacks = structuredStackTrace
61
- . map ( ( callSite ) => createWebAssemblyCallSite ( callSite , { wasmPath, sourceMapUrl } ) )
116
+ . map ( ( callSite ) => createWebAssemblyCallSite ( callSite , { wasmPath, sourceMapConsumer } ) )
62
117
. filter ( ( callSite ) => callSite != null ) ;
63
118
} ;
64
119
error . stack ; // trigger prepareStackTrace
65
120
Error . prepareStackTrace = originalPrepareStackTrace ;
66
- return new ExecutionError ( error . message , stacks ) ;
121
+ return { message : error . message , stacks } ;
67
122
}
0 commit comments