warpo 3.1.0 → 3.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/debug_server/debugPath.d.ts +1 -0
- package/dist/debug_server/debugPath.js +10 -0
- package/dist/debug_server/debugPath.js.map +1 -0
- package/dist/debug_server/debugSession.d.ts +60 -2
- package/dist/debug_server/debugSession.js +883 -40
- package/dist/debug_server/debugSession.js.map +1 -1
- package/dist/debug_server/debugger.d.ts +30 -2
- package/dist/debug_server/debuggerWasmModule.d.ts +58 -3
- package/dist/debug_server/debuggerWasmModule.js +222 -14
- package/dist/debug_server/debuggerWasmModule.js.map +1 -1
- package/dist/debug_server/nodeDebugger.d.ts +37 -3
- package/dist/debug_server/nodeDebugger.js +590 -59
- package/dist/debug_server/nodeDebugger.js.map +1 -1
- package/dist/debug_server/tests/debugSession.test.js +1015 -26
- package/dist/debug_server/tests/debugSession.test.js.map +1 -1
- package/dist/debug_server/tests/launcher.js +1 -0
- package/dist/debug_server/tests/launcher.js.map +1 -1
- package/dist/debug_server/tests/testModule/debugger_array.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_array.js +9 -0
- package/dist/debug_server/tests/testModule/debugger_array.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_array_buffer.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_array_buffer.js +12 -0
- package/dist/debug_server/tests/testModule/debugger_array_buffer.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_basic.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_basic.js +9 -1
- package/dist/debug_server/tests/testModule/debugger_basic.js.map +1 -1
- package/dist/debug_server/tests/testModule/debugger_callee.d.ts +10 -0
- package/dist/debug_server/tests/testModule/debugger_callee.js +106 -0
- package/dist/debug_server/tests/testModule/debugger_callee.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_caller.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_caller.js +7 -0
- package/dist/debug_server/tests/testModule/debugger_caller.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_class.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_class.js +39 -0
- package/dist/debug_server/tests/testModule/debugger_class.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_class_array.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_class_array.js +15 -0
- package/dist/debug_server/tests/testModule/debugger_class_array.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_closure.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_closure.js +48 -0
- package/dist/debug_server/tests/testModule/debugger_closure.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_closure_if.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_closure_if.js +16 -0
- package/dist/debug_server/tests/testModule/debugger_closure_if.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_global.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_global.js +11 -0
- package/dist/debug_server/tests/testModule/debugger_global.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_map.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_map.js +22 -0
- package/dist/debug_server/tests/testModule/debugger_map.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_scopes.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_scopes.js +22 -0
- package/dist/debug_server/tests/testModule/debugger_scopes.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_set.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_set.js +23 -0
- package/dist/debug_server/tests/testModule/debugger_set.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_static_array.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_static_array.js +21 -0
- package/dist/debug_server/tests/testModule/debugger_static_array.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_string.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_string.js +16 -0
- package/dist/debug_server/tests/testModule/debugger_string.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_trap.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_trap.js +9 -0
- package/dist/debug_server/tests/testModule/debugger_trap.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_tuple.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_tuple.js +15 -0
- package/dist/debug_server/tests/testModule/debugger_tuple.js.map +1 -0
- package/dist/debug_server/tests/testModule/import_failure.d.ts +1 -0
- package/dist/debug_server/tests/testModule/import_failure.js +4 -0
- package/dist/debug_server/tests/testModule/import_failure.js.map +1 -0
- package/dist/debug_server/tests/testModule/unittest_debug.case.js +10 -0
- package/dist/debug_server/tests/testModule/unittest_debug.case.js.map +1 -0
- package/dist/debug_server/tests/testModule/unittest_debug.d.ts +1 -0
- package/dist/debug_server/tests/testModule/unittest_debug.js +6 -0
- package/dist/debug_server/tests/testModule/unittest_debug.js.map +1 -0
- package/dist/debug_server/wasmEntry.js +32 -12
- package/dist/debug_server/wasmEntry.js.map +1 -1
- package/dist/dwarf/classDebugInfo.d.ts +45 -0
- package/dist/dwarf/classDebugInfo.js +292 -0
- package/dist/dwarf/classDebugInfo.js.map +1 -0
- package/dist/{heap_analyzer/src → dwarf}/dwarfParser.js +2 -0
- package/dist/dwarf/dwarfParser.js.map +1 -0
- package/dist/dwarf/functionDebugInfo.d.ts +63 -0
- package/dist/dwarf/functionDebugInfo.js +377 -0
- package/dist/dwarf/functionDebugInfo.js.map +1 -0
- package/dist/dwarf/tests/fixtures/functionDebugInfo.d.ts +3 -0
- package/dist/dwarf/tests/fixtures/functionDebugInfo.js +24 -0
- package/dist/dwarf/tests/fixtures/functionDebugInfo.js.map +1 -0
- package/dist/dwarf/tests/functionDebugInfo.test.d.ts +1 -0
- package/dist/dwarf/tests/functionDebugInfo.test.js +102 -0
- package/dist/dwarf/tests/functionDebugInfo.test.js.map +1 -0
- package/dist/heap_analyzer/cli.d.ts +1 -0
- package/dist/heap_analyzer/cli.js +172 -0
- package/dist/heap_analyzer/cli.js.map +1 -0
- package/dist/heap_analyzer/src/constants.js +2 -1
- package/dist/heap_analyzer/src/constants.js.map +1 -1
- package/dist/heap_analyzer/src/debugInfoResolver.d.ts +4 -15
- package/dist/heap_analyzer/src/debugInfoResolver.js +20 -231
- package/dist/heap_analyzer/src/debugInfoResolver.js.map +1 -1
- package/dist/heap_analyzer/src/dominator.js +4 -1
- package/dist/heap_analyzer/src/dominator.js.map +1 -1
- package/dist/heap_analyzer/src/referenceScanner.js +7 -7
- package/dist/heap_analyzer/src/referenceScanner.js.map +1 -1
- package/dist/heap_analyzer/src/retainedSize.js +2 -0
- package/dist/heap_analyzer/src/retainedSize.js.map +1 -1
- package/dist/heap_analyzer/src/snapshot.js +1 -1
- package/dist/heap_analyzer/src/snapshot.js.map +1 -1
- package/dist/heap_analyzer/src/{tlsf.js → tlsfBlockWalker.js} +4 -3
- package/dist/heap_analyzer/src/tlsfBlockWalker.js.map +1 -0
- package/dist/heap_analyzer/src/types.d.ts +3 -32
- package/dist/heap_analyzer/src/types.js +1 -8
- package/dist/heap_analyzer/src/types.js.map +1 -1
- package/dist/heap_analyzer/tests/debugInfoResolver.test.js +1 -1
- package/dist/heap_analyzer/tests/debugInfoResolver.test.js.map +1 -1
- package/dist/heap_analyzer/tests/dwarfParser.test.js +57 -40
- package/dist/heap_analyzer/tests/dwarfParser.test.js.map +1 -1
- package/dist/heap_analyzer/tests/roots.test.js +1 -1
- package/dist/heap_analyzer/tests/roots.test.js.map +1 -1
- package/dist/heap_analyzer/tests/snapshot.test.js +4 -4
- package/dist/heap_analyzer/tests/testHelper.js +4 -7
- package/dist/heap_analyzer/tests/testHelper.js.map +1 -1
- package/dist/heap_analyzer/tests/tlsfBlockWalker.test.d.ts +1 -0
- package/dist/heap_analyzer/tests/{tlsf.test.js → tlsfBlockWalker.test.js} +2 -2
- package/dist/heap_analyzer/tests/tlsfBlockWalker.test.js.map +1 -0
- package/dist/heap_analyzer/tests/wasmExecutor.d.ts +2 -2
- package/dist/heap_analyzer/tests/wasmExecutor.js +5 -5
- package/dist/heap_analyzer/tests/wasmExecutor.js.map +1 -1
- package/dist/runtime/objectLayout.d.ts +10 -0
- package/dist/runtime/objectLayout.js +13 -0
- package/dist/runtime/objectLayout.js.map +1 -0
- package/dist/scripts/lib.d.ts +1 -0
- package/dist/scripts/lib.js +86 -22
- package/dist/scripts/lib.js.map +1 -1
- package/dist/test_runner/cli.d.ts +0 -1
- package/dist/test_runner/cli.js +37 -19
- package/dist/test_runner/cli.js.map +1 -1
- package/dist/test_runner/config.d.ts +3 -0
- package/dist/test_runner/config.js +8 -0
- package/dist/test_runner/config.js.map +1 -0
- package/dist/test_runner/core/analyze.js +12 -3
- package/dist/test_runner/core/analyze.js.map +1 -1
- package/dist/test_runner/core/compile.js +1 -4
- package/dist/test_runner/core/compile.js.map +1 -1
- package/dist/test_runner/core/execute.js +20 -3
- package/dist/test_runner/core/execute.js.map +1 -1
- package/dist/test_runner/executionResult.js +10 -8
- package/dist/test_runner/executionResult.js.map +1 -1
- package/dist/test_runner/generator/table-generator/index.js +4 -2
- package/dist/test_runner/generator/table-generator/index.js.map +1 -1
- package/dist/test_runner/parser/index.js +6 -5
- package/dist/test_runner/parser/index.js.map +1 -1
- package/dist/test_runner/parser/singleFileAnalysis.js +7 -4
- package/dist/test_runner/parser/singleFileAnalysis.js.map +1 -1
- package/dist/test_runner/parser/singleFunctionAnalysis.js +1 -1
- package/dist/test_runner/parser/singleFunctionAnalysis.js.map +1 -1
- package/dist/test_runner/utils/errorTraceHandler.js +2 -1
- package/dist/test_runner/utils/errorTraceHandler.js.map +1 -1
- package/dist/test_runner/utils/index.js +9 -2
- package/dist/test_runner/utils/index.js.map +1 -1
- package/dist/test_runner/utils/interface.js +2 -1
- package/dist/test_runner/utils/interface.js.map +1 -1
- package/dist/test_runner/utils/pathResolver.js +9 -7
- package/dist/test_runner/utils/pathResolver.js.map +1 -1
- package/dist/test_runner/utils/wasm.js +16 -8
- package/dist/test_runner/utils/wasm.js.map +1 -1
- package/dist/trace_visualizer.d.ts +2 -0
- package/dist/trace_visualizer.js +18 -0
- package/dist/trace_visualizer.js.map +1 -0
- package/dist/warpo.js +2 -1
- package/dist/warpo.js.map +1 -1
- package/package.json +10 -5
- package/dist/heap_analyzer/src/dwarfParser.js.map +0 -1
- package/dist/heap_analyzer/src/tlsf.js.map +0 -1
- package/dist/heap_analyzer/tests/tlsf.test.js.map +0 -1
- /package/dist/{heap_analyzer/tests/tlsf.test.d.ts → debug_server/tests/testModule/unittest_debug.case.d.ts} +0 -0
- /package/dist/{heap_analyzer/src → dwarf}/dwarfParser.d.ts +0 -0
- /package/dist/heap_analyzer/src/{tlsf.d.ts → tlsfBlockWalker.d.ts} +0 -0
|
@@ -9,6 +9,8 @@ import { DebuggerWasmModule } from "./debuggerWasmModule.js";
|
|
|
9
9
|
const DIRNAME = path.dirname(fileURLToPath(import.meta.url));
|
|
10
10
|
const DEBUG_SERVER_TRACE_ENABLED = process.env.WARPO_DEBUG_SERVER_TRACE === "1";
|
|
11
11
|
const DEBUG_SERVER_TRACE_FILE = path.join(process.cwd(), ".warpo-debug-server-trace.log");
|
|
12
|
+
const LINEAR_MEMORY_OBJECT_GROUP = "warpo-linear-memory";
|
|
13
|
+
const NODE_WAITING_FOR_DEBUGGER_DISCONNECT = "Waiting for the debugger to disconnect...";
|
|
12
14
|
if (DEBUG_SERVER_TRACE_ENABLED) {
|
|
13
15
|
try {
|
|
14
16
|
writeFileSync(DEBUG_SERVER_TRACE_FILE, "");
|
|
@@ -27,6 +29,25 @@ function trace(message) {
|
|
|
27
29
|
}
|
|
28
30
|
}
|
|
29
31
|
}
|
|
32
|
+
const READ_WASM_MEMORY_FUNCTION = function (address, byteLength) {
|
|
33
|
+
const buffer = this instanceof ArrayBuffer || (typeof SharedArrayBuffer !== "undefined" && this instanceof SharedArrayBuffer)
|
|
34
|
+
? this
|
|
35
|
+
: this.buffer;
|
|
36
|
+
if (!(buffer instanceof ArrayBuffer) &&
|
|
37
|
+
!(typeof SharedArrayBuffer !== "undefined" && buffer instanceof SharedArrayBuffer)) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (address < 0 || byteLength < 0 || address + byteLength > buffer.byteLength) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
return Array.from(new Uint8Array(buffer, address, byteLength));
|
|
44
|
+
}.toString();
|
|
45
|
+
const GET_WASM_MEMORY_BUFFER_FUNCTION = function () {
|
|
46
|
+
if (this instanceof ArrayBuffer || (typeof SharedArrayBuffer !== "undefined" && this instanceof SharedArrayBuffer)) {
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
return this.buffer;
|
|
50
|
+
}.toString();
|
|
30
51
|
function findFreePort() {
|
|
31
52
|
return new Promise((resolve, reject) => {
|
|
32
53
|
const srv = net.createServer();
|
|
@@ -51,28 +72,97 @@ export class NodeDebugger {
|
|
|
51
72
|
name = "node";
|
|
52
73
|
onModuleLoad;
|
|
53
74
|
onPause;
|
|
75
|
+
onRuntimeError;
|
|
76
|
+
onRuntimeExit;
|
|
77
|
+
onLog;
|
|
54
78
|
child;
|
|
55
79
|
ws;
|
|
56
80
|
cdpId = 0;
|
|
57
81
|
pendingCommandCallbacks = new Map();
|
|
58
82
|
paused = false;
|
|
59
83
|
wasmFilePath;
|
|
84
|
+
wasmScriptId;
|
|
85
|
+
pausedCallFrames = [];
|
|
86
|
+
wasmMemoryBufferObjectId;
|
|
87
|
+
wasmGlobalsObjectId;
|
|
88
|
+
stderrOutput = "";
|
|
89
|
+
disposed = false;
|
|
90
|
+
runtimeExitPromise;
|
|
91
|
+
log(message) {
|
|
92
|
+
trace(message);
|
|
93
|
+
this.onLog?.(`[${this.name}] ${message}`);
|
|
94
|
+
}
|
|
60
95
|
async launch(config) {
|
|
96
|
+
this.disposed = false;
|
|
61
97
|
this.wasmFilePath = config.wasmFilePath;
|
|
62
98
|
const port = await findFreePort();
|
|
63
|
-
this.
|
|
64
|
-
|
|
65
|
-
path.join(DIRNAME, "
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
99
|
+
this.log(`launch wasmFilePath=${config.wasmFilePath} inspectPort=${port}`);
|
|
100
|
+
const launchArgs = "cwd" in config
|
|
101
|
+
? [`--inspect-brk=${port}`, config.warpoPath ?? path.join(DIRNAME, "..", "warpo.js"), "test"]
|
|
102
|
+
: [
|
|
103
|
+
`--inspect-brk=${port}`,
|
|
104
|
+
path.join(DIRNAME, "wasmEntry.js"),
|
|
105
|
+
config.wasmFilePath,
|
|
106
|
+
config.entryFunctionName,
|
|
107
|
+
...config.args.map(String),
|
|
108
|
+
];
|
|
109
|
+
this.child = spawn(process.execPath, launchArgs, {
|
|
110
|
+
cwd: "cwd" in config ? config.cwd : undefined,
|
|
111
|
+
env: getRuntimeEnv(),
|
|
112
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
113
|
+
});
|
|
114
|
+
this.log(`runtime child spawned pid=${this.child.pid ?? "unknown"}`);
|
|
115
|
+
this.runtimeExitPromise = new Promise((resolve) => {
|
|
116
|
+
this.child?.once("exit", () => resolve());
|
|
117
|
+
this.child?.once("error", () => resolve());
|
|
118
|
+
});
|
|
119
|
+
this.child.stdout?.on("data", (chunk) => {
|
|
120
|
+
const text = chunk.toString().trim();
|
|
121
|
+
if (text) {
|
|
122
|
+
this.log(`runtime stdout: ${text}`);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
this.child.stderr?.on("data", (chunk) => {
|
|
126
|
+
const stderr = parseRuntimeStderr(chunk.toString());
|
|
127
|
+
this.stderrOutput += stderr.output;
|
|
128
|
+
const text = stderr.output.trim();
|
|
129
|
+
if (text) {
|
|
130
|
+
this.log(`runtime stderr: ${text}`);
|
|
131
|
+
}
|
|
132
|
+
if (stderr.waitingForDebuggerDisconnect) {
|
|
133
|
+
this.log("runtime waiting for debugger disconnect -> close websocket");
|
|
134
|
+
this.ws?.close();
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
this.child.on("error", (error) => {
|
|
138
|
+
this.log(`runtime child error: ${error.message}`);
|
|
139
|
+
this.onRuntimeError?.(error.message);
|
|
140
|
+
this.onRuntimeExit?.();
|
|
141
|
+
});
|
|
142
|
+
this.child.on("exit", (code, signal) => {
|
|
143
|
+
this.log(`runtime exit code=${code ?? "null"} signal=${signal ?? "null"}`);
|
|
144
|
+
if (this.disposed) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (code && code !== 0) {
|
|
148
|
+
this.onRuntimeError?.(this.stderrOutput.trim() || `Debug runtime exited with code ${code}`);
|
|
149
|
+
}
|
|
150
|
+
this.onRuntimeExit?.();
|
|
151
|
+
});
|
|
70
152
|
const wsUrl = await this.fetchWsUrl(port);
|
|
71
|
-
|
|
153
|
+
this.log(`inspector websocket discovered: ${wsUrl}`);
|
|
154
|
+
await Promise.race([this.connectCDP(wsUrl), this.runtimeExitPromise]);
|
|
72
155
|
}
|
|
73
156
|
dispose() {
|
|
157
|
+
this.disposed = true;
|
|
74
158
|
this.paused = false;
|
|
75
159
|
this.wasmFilePath = undefined;
|
|
160
|
+
this.wasmScriptId = undefined;
|
|
161
|
+
this.pausedCallFrames = [];
|
|
162
|
+
this.wasmMemoryBufferObjectId = undefined;
|
|
163
|
+
this.wasmGlobalsObjectId = undefined;
|
|
164
|
+
this.stderrOutput = "";
|
|
165
|
+
this.runtimeExitPromise = undefined;
|
|
76
166
|
this.ws?.close();
|
|
77
167
|
this.child?.kill();
|
|
78
168
|
}
|
|
@@ -85,28 +175,114 @@ export class NodeDebugger {
|
|
|
85
175
|
async resume() {
|
|
86
176
|
await this.waitForCommand("Debugger.resume");
|
|
87
177
|
this.paused = false;
|
|
178
|
+
this.pausedCallFrames = [];
|
|
179
|
+
this.wasmMemoryBufferObjectId = undefined;
|
|
180
|
+
this.wasmGlobalsObjectId = undefined;
|
|
88
181
|
}
|
|
89
182
|
finishModuleLoad() {
|
|
183
|
+
this.log("finishModuleLoad -> Runtime.runIfWaitingForDebugger");
|
|
90
184
|
this.sendCommand("Runtime.runIfWaitingForDebugger");
|
|
91
185
|
}
|
|
92
|
-
setWasmBreakpoint(module,
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
186
|
+
setWasmBreakpoint(module, wasmBytecodeOffset, callbacks) {
|
|
187
|
+
void (async () => {
|
|
188
|
+
try {
|
|
189
|
+
const location = this.resolveWasmBytecodeOffsetLocation(module, wasmBytecodeOffset);
|
|
190
|
+
this.log(`setWasmBreakpoint offset=${wasmBytecodeOffset} url=${module.url}`);
|
|
191
|
+
await this.waitForCommandResult("Debugger.setBreakpointByUrl", {
|
|
192
|
+
lineNumber: location.lineNumber,
|
|
193
|
+
url: module.url,
|
|
194
|
+
columnNumber: location.columnNumber,
|
|
195
|
+
condition: "",
|
|
196
|
+
});
|
|
197
|
+
this.log(`setWasmBreakpoint success offset=${wasmBytecodeOffset}`);
|
|
100
198
|
callbacks?.onSuccess?.();
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
const commandError = error instanceof Error ? error : new Error(String(error));
|
|
202
|
+
this.log(`setWasmBreakpoint error offset=${wasmBytecodeOffset} message=${commandError.message}`);
|
|
203
|
+
callbacks?.onError?.(commandError);
|
|
204
|
+
}
|
|
205
|
+
})();
|
|
206
|
+
}
|
|
207
|
+
resolveWasmBytecodeOffsetLocation(module, wasmBytecodeOffset) {
|
|
208
|
+
if (!module.url) {
|
|
209
|
+
throw new Error("Wasm script URL is not available");
|
|
210
|
+
}
|
|
211
|
+
return { scriptId: module.scriptId, lineNumber: 0, columnNumber: wasmBytecodeOffset };
|
|
212
|
+
}
|
|
213
|
+
resolveWasmLocationBytecodeOffset(location) {
|
|
214
|
+
if (location.lineNumber !== 0) {
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
return location.columnNumber;
|
|
218
|
+
}
|
|
219
|
+
getPausedWasmFrames() {
|
|
220
|
+
const frames = this.pausedCallFrames.map((callFrame) => this.toPausedWasmFrame(callFrame));
|
|
221
|
+
this.log(`getPausedWasmFrames count=${frames.length}`);
|
|
222
|
+
for (const [index, frame] of frames.entries()) {
|
|
223
|
+
this.log(` wasm frame ${index}: name=${frame.functionName}, offset=${frame.wasmBytecodeOffset ?? "unknown"}`);
|
|
224
|
+
}
|
|
225
|
+
return frames;
|
|
226
|
+
}
|
|
227
|
+
async getPausedWasmFrameVariables(frameIndex) {
|
|
228
|
+
const callFrame = this.pausedCallFrames[frameIndex];
|
|
229
|
+
if (!callFrame) {
|
|
230
|
+
this.log(`getPausedWasmFrameVariables frameIndex=${frameIndex} unavailable`);
|
|
231
|
+
return [];
|
|
232
|
+
}
|
|
233
|
+
const variables = await this.getLocalScopeVariables(callFrame);
|
|
234
|
+
this.log(`getPausedWasmFrameVariables frameIndex=${frameIndex} variables=${variables.length}`);
|
|
235
|
+
return variables;
|
|
236
|
+
}
|
|
237
|
+
async getPausedWasmGlobalVariables() {
|
|
238
|
+
const globalsObjectId = await this.getWasmGlobalsObjectId();
|
|
239
|
+
if (!globalsObjectId) {
|
|
240
|
+
return [];
|
|
241
|
+
}
|
|
242
|
+
const globalsResult = await this.waitForCommandResult("Runtime.getProperties", {
|
|
243
|
+
objectId: globalsObjectId,
|
|
244
|
+
ownProperties: true,
|
|
106
245
|
});
|
|
246
|
+
const globals = Array.isArray(globalsResult.result) ? globalsResult.result.filter(isCDPPropertyDescriptor) : [];
|
|
247
|
+
const values = [];
|
|
248
|
+
for (const [globalIndex, global] of globals.entries()) {
|
|
249
|
+
if (!global.value) {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
values.push({
|
|
253
|
+
globalIndex,
|
|
254
|
+
value: await this.readRemoteObjectValue(global.value),
|
|
255
|
+
type: global.value.description ?? global.value.type,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
return values;
|
|
259
|
+
}
|
|
260
|
+
async readWasmMemory(address, byteLength) {
|
|
261
|
+
this.log(`readWasmMemory address=${address} byteLength=${byteLength}`);
|
|
262
|
+
const memoryBufferObjectId = await this.getWasmMemoryBufferObjectId();
|
|
263
|
+
if (!memoryBufferObjectId) {
|
|
264
|
+
this.log("readWasmMemory skipped: memory buffer unavailable");
|
|
265
|
+
return undefined;
|
|
266
|
+
}
|
|
267
|
+
this.log("readWasmMemory -> Runtime.callFunctionOn");
|
|
268
|
+
const result = await this.waitForCommandResult("Runtime.callFunctionOn", {
|
|
269
|
+
objectId: memoryBufferObjectId,
|
|
270
|
+
functionDeclaration: READ_WASM_MEMORY_FUNCTION,
|
|
271
|
+
arguments: [{ value: address }, { value: byteLength }],
|
|
272
|
+
silent: true,
|
|
273
|
+
returnByValue: true,
|
|
274
|
+
});
|
|
275
|
+
const remoteObject = getRuntimeEvaluateRemoteObject(result);
|
|
276
|
+
if (!Array.isArray(remoteObject?.value) || !remoteObject.value.every((value) => typeof value === "number")) {
|
|
277
|
+
this.log("readWasmMemory failed: runtime returned non-byte-array value");
|
|
278
|
+
return undefined;
|
|
279
|
+
}
|
|
280
|
+
this.log(`readWasmMemory success bytes=${remoteObject.value.length}`);
|
|
281
|
+
return Uint8Array.from(remoteObject.value);
|
|
107
282
|
}
|
|
108
283
|
async fetchWsUrl(port) {
|
|
109
284
|
const deadline = Date.now() + 5000;
|
|
285
|
+
this.log(`waiting for inspector endpoint on port ${port}`);
|
|
110
286
|
while (Date.now() < deadline) {
|
|
111
287
|
try {
|
|
112
288
|
const resp = await fetch(`http://127.0.0.1:${port}/json`);
|
|
@@ -115,10 +291,12 @@ export class NodeDebugger {
|
|
|
115
291
|
}
|
|
116
292
|
const [info] = (await resp.json());
|
|
117
293
|
if (info?.webSocketDebuggerUrl) {
|
|
294
|
+
this.log(`inspector endpoint ready on port ${port}`);
|
|
118
295
|
return info.webSocketDebuggerUrl;
|
|
119
296
|
}
|
|
120
297
|
}
|
|
121
|
-
catch {
|
|
298
|
+
catch (error) {
|
|
299
|
+
trace(`inspector endpoint not ready: ${error instanceof Error ? error.message : "unknown error"}`);
|
|
122
300
|
// The inspector endpoint is not always ready immediately after spawn.
|
|
123
301
|
}
|
|
124
302
|
await wait(50);
|
|
@@ -133,15 +311,33 @@ export class NodeDebugger {
|
|
|
133
311
|
if (callbacks) {
|
|
134
312
|
this.pendingCommandCallbacks.set(id, callbacks);
|
|
135
313
|
}
|
|
314
|
+
this.log(`CDP -> ${method} id=${id}`);
|
|
136
315
|
this.ws.send(JSON.stringify({ id, method, params }));
|
|
316
|
+
return id;
|
|
137
317
|
}
|
|
138
318
|
waitForCommand(method, params) {
|
|
139
319
|
return new Promise((resolve, reject) => {
|
|
140
|
-
this.sendCommand(method, params, {
|
|
320
|
+
const id = this.sendCommand(method, params, {
|
|
141
321
|
onSuccess: () => {
|
|
322
|
+
this.log(`CDP command completed ${method} id=${id}`);
|
|
142
323
|
resolve();
|
|
143
324
|
},
|
|
144
325
|
onError: (error) => {
|
|
326
|
+
this.log(`CDP command failed ${method} id=${id}: ${error.message}`);
|
|
327
|
+
reject(error);
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
waitForCommandResult(method, params) {
|
|
333
|
+
return new Promise((resolve, reject) => {
|
|
334
|
+
const id = this.sendCommand(method, params, {
|
|
335
|
+
onSuccess: (result) => {
|
|
336
|
+
this.log(`CDP command completed ${method} id=${id}`);
|
|
337
|
+
resolve(result ?? {});
|
|
338
|
+
},
|
|
339
|
+
onError: (error) => {
|
|
340
|
+
this.log(`CDP command failed ${method} id=${id}: ${error.message}`);
|
|
145
341
|
reject(error);
|
|
146
342
|
},
|
|
147
343
|
});
|
|
@@ -154,60 +350,143 @@ export class NodeDebugger {
|
|
|
154
350
|
ws.addEventListener("open", () => {
|
|
155
351
|
void (async () => {
|
|
156
352
|
try {
|
|
157
|
-
|
|
353
|
+
this.log("websocket open -> Runtime.enable");
|
|
354
|
+
await this.waitForCommand("Runtime.enable");
|
|
355
|
+
this.log("Runtime.enable complete -> Debugger.enable");
|
|
158
356
|
await this.waitForCommand("Debugger.enable");
|
|
159
|
-
|
|
160
|
-
await this.waitForCommand("Debugger.setBreakpointsActive", { active: true });
|
|
161
|
-
trace("Debugger.setBreakpointsActive -> Runtime.runIfWaitingForDebugger");
|
|
357
|
+
this.log("Debugger.enable complete -> Runtime.runIfWaitingForDebugger");
|
|
162
358
|
await this.waitForCommand("Runtime.runIfWaitingForDebugger");
|
|
163
|
-
|
|
359
|
+
this.log("Runtime.runIfWaitingForDebugger complete -> Debugger.setBreakpointsActive");
|
|
360
|
+
await this.waitForCommand("Debugger.setBreakpointsActive", { active: true });
|
|
361
|
+
this.log("Debugger.setBreakpointsActive complete -> Debugger.setPauseOnExceptions");
|
|
362
|
+
await this.waitForCommand("Debugger.setPauseOnExceptions", { state: "all" });
|
|
363
|
+
this.log("Debugger.setPauseOnExceptions complete -> launch ready");
|
|
164
364
|
resolve();
|
|
165
365
|
}
|
|
166
366
|
catch (error) {
|
|
367
|
+
this.log(`CDP setup failed: ${error instanceof Error ? error.message : "unknown error"}`);
|
|
167
368
|
reject(error instanceof Error ? error : new Error(String(error)));
|
|
168
369
|
}
|
|
169
370
|
})();
|
|
170
371
|
});
|
|
171
372
|
ws.addEventListener("error", (e) => {
|
|
172
373
|
const message = e instanceof Error ? e.message : "WebSocket connection failed";
|
|
374
|
+
this.log(`websocket error: ${message}`);
|
|
173
375
|
reject(new Error(message));
|
|
174
376
|
});
|
|
377
|
+
ws.addEventListener("close", (event) => {
|
|
378
|
+
this.log(`websocket close code=${event.code} reason=${event.reason || ""}`);
|
|
379
|
+
});
|
|
175
380
|
ws.addEventListener("message", (ev) => {
|
|
176
381
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
177
382
|
const msg = JSON.parse(String(ev.data));
|
|
178
|
-
|
|
179
|
-
this.handleCommandResponse(msg.id, msg);
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
if (msg.method === "Debugger.paused") {
|
|
183
|
-
const reason = this.getPauseReason(msg.params);
|
|
184
|
-
this.paused = true;
|
|
185
|
-
trace(`Debugger.paused reason=${reason}`);
|
|
186
|
-
void Promise.resolve(this.onPause?.({ reason })).catch((error) => {
|
|
187
|
-
trace(`pause handler failed: ${error instanceof Error ? error.message : "unknown error"}`);
|
|
188
|
-
});
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
if (msg.method === "Debugger.scriptParsed") {
|
|
192
|
-
const params = msg.params;
|
|
193
|
-
const lang = typeof params?.scriptLanguage === "string" ? params.scriptLanguage : "unknown";
|
|
194
|
-
const url = typeof params?.url === "string" ? params.url : "";
|
|
195
|
-
trace(`Debugger.scriptParsed language=${lang} url=${url}`);
|
|
196
|
-
if (params && lang === "WebAssembly" && typeof params.scriptId === "string") {
|
|
197
|
-
trace(`wasm module detected scriptId=${params.scriptId}`);
|
|
198
|
-
void this.handleModuleLoad({
|
|
199
|
-
scriptId: params.scriptId,
|
|
200
|
-
url,
|
|
201
|
-
}).catch((error) => {
|
|
202
|
-
const message = error instanceof Error ? error.message : "unknown error";
|
|
203
|
-
trace(`module load handler failed: ${message}`);
|
|
204
|
-
process.stderr.write(`[node-debugger] module load handler failed: ${message}\n`);
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
}
|
|
383
|
+
this.handleCdpMessage(msg);
|
|
208
384
|
});
|
|
209
385
|
});
|
|
210
386
|
}
|
|
387
|
+
handleCdpMessage(msg) {
|
|
388
|
+
if (typeof msg.id === "number") {
|
|
389
|
+
this.handleCommandResponse(msg.id, msg);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
if (msg.method === "Debugger.paused") {
|
|
393
|
+
this.handleDebuggerPaused(msg.params);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
if (msg.method === "Debugger.scriptParsed") {
|
|
397
|
+
this.handleScriptParsed(msg.params);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
handleDebuggerPaused(params) {
|
|
401
|
+
const reason = this.getPauseReason(params);
|
|
402
|
+
this.pausedCallFrames = this.getPausedWasmCallFrames(params);
|
|
403
|
+
this.wasmMemoryBufferObjectId = undefined;
|
|
404
|
+
this.wasmGlobalsObjectId = undefined;
|
|
405
|
+
if (reason === "Break on start" && !this.wasmScriptId) {
|
|
406
|
+
this.log("Debugger.paused reason=Break on start location=inspect-brk startup");
|
|
407
|
+
void this.resumeInternalStartupPause();
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
if (reason === "other" && this.wasmScriptId) {
|
|
411
|
+
this.log("Debugger.paused reason=other location=wasm pre-entry gate");
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
const isExceptionPause = reason === "exception" || reason === "promiseRejection";
|
|
415
|
+
// CDP can only pause on exceptions for the whole Node process, not for a specific wasm module. We need
|
|
416
|
+
// "all" because the JavaScript wrapper can catch a wasm trap, but that also pauses for unrelated JavaScript
|
|
417
|
+
// exceptions. Treat the top frame belonging to our wasm module as the filter for a debugger-visible trap.
|
|
418
|
+
if (isExceptionPause && !this.isTopFrameWasm(params)) {
|
|
419
|
+
this.log(`Debugger.paused reason=${reason} outside wasm -> resume`);
|
|
420
|
+
void this.resumeIgnoredPause();
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
const isWasmTrap = isExceptionPause;
|
|
424
|
+
if (isWasmTrap) {
|
|
425
|
+
this.paused = true;
|
|
426
|
+
this.log(`Debugger.paused reason=${reason} (wasm trap)`);
|
|
427
|
+
void this.notifyPause("exception");
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
this.paused = true;
|
|
431
|
+
this.log(`Debugger.paused reason=${reason}`);
|
|
432
|
+
void this.notifyPause(reason);
|
|
433
|
+
}
|
|
434
|
+
async notifyPause(reason) {
|
|
435
|
+
try {
|
|
436
|
+
const pausedCallFrame = this.pausedCallFrames[0];
|
|
437
|
+
const wasmBytecodeOffset = pausedCallFrame
|
|
438
|
+
? this.resolveWasmLocationBytecodeOffset(pausedCallFrame.location)
|
|
439
|
+
: undefined;
|
|
440
|
+
await this.onPause?.({ reason, wasmBytecodeOffset });
|
|
441
|
+
}
|
|
442
|
+
catch (error) {
|
|
443
|
+
this.log(`pause handler failed: ${error instanceof Error ? error.message : "unknown error"}`);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
handleScriptParsed(params) {
|
|
447
|
+
const lang = typeof params?.scriptLanguage === "string" ? params.scriptLanguage : "unknown";
|
|
448
|
+
const url = typeof params?.url === "string" ? params.url : "";
|
|
449
|
+
this.log(`Debugger.scriptParsed language=${lang} url=${url}`);
|
|
450
|
+
if (!params || lang !== "WebAssembly" || typeof params.scriptId !== "string") {
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
if (!this.isExpectedWasmModule(params)) {
|
|
454
|
+
this.log(`ignoring non-target wasm module scriptId=${params.scriptId} url=${url}`);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
this.wasmScriptId = params.scriptId;
|
|
458
|
+
this.log(`wasm module detected scriptId=${params.scriptId}`);
|
|
459
|
+
void this.handleModuleLoad({
|
|
460
|
+
scriptId: params.scriptId,
|
|
461
|
+
url,
|
|
462
|
+
}).catch((error) => {
|
|
463
|
+
const message = error instanceof Error ? error.message : "unknown error";
|
|
464
|
+
this.log(`module load handler failed: ${message}`);
|
|
465
|
+
process.stderr.write(`Module load handler failed: ${message}\n`);
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
isExpectedWasmModule(params) {
|
|
469
|
+
if (!this.wasmFilePath) {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
return params.sourceMapURL === `${path.basename(this.wasmFilePath)}.map`;
|
|
473
|
+
}
|
|
474
|
+
async resumeInternalStartupPause() {
|
|
475
|
+
await this.resumeIgnoredPause("inspect-brk startup pause");
|
|
476
|
+
}
|
|
477
|
+
async resumeIgnoredPause(description = "non-wasm exception pause") {
|
|
478
|
+
try {
|
|
479
|
+
await this.waitForCommand("Debugger.resume");
|
|
480
|
+
this.paused = false;
|
|
481
|
+
this.pausedCallFrames = [];
|
|
482
|
+
this.wasmMemoryBufferObjectId = undefined;
|
|
483
|
+
this.wasmGlobalsObjectId = undefined;
|
|
484
|
+
this.log(`${description} resumed`);
|
|
485
|
+
}
|
|
486
|
+
catch (error) {
|
|
487
|
+
this.log(`failed to resume ${description}: ${error instanceof Error ? error.message : "unknown error"}`);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
211
490
|
handleCommandResponse(id, msg) {
|
|
212
491
|
const callbacks = this.pendingCommandCallbacks.get(id);
|
|
213
492
|
if (!callbacks) {
|
|
@@ -215,10 +494,12 @@ export class NodeDebugger {
|
|
|
215
494
|
}
|
|
216
495
|
this.pendingCommandCallbacks.delete(id);
|
|
217
496
|
if (msg.error) {
|
|
497
|
+
this.log(`CDP <- error id=${id} message=${msg.error.message ?? "CDP command failed"}`);
|
|
218
498
|
callbacks.onError?.(new Error(msg.error.message ?? "CDP command failed"));
|
|
219
499
|
return;
|
|
220
500
|
}
|
|
221
|
-
|
|
501
|
+
this.log(`CDP <- success id=${id}`);
|
|
502
|
+
callbacks.onSuccess?.(msg.result);
|
|
222
503
|
}
|
|
223
504
|
getPauseReason(params) {
|
|
224
505
|
if (Array.isArray(params?.hitBreakpoints) && params.hitBreakpoints.length > 0) {
|
|
@@ -229,12 +510,262 @@ export class NodeDebugger {
|
|
|
229
510
|
}
|
|
230
511
|
return "unknown";
|
|
231
512
|
}
|
|
513
|
+
getPausedWasmCallFrames(params) {
|
|
514
|
+
if (!Array.isArray(params?.callFrames)) {
|
|
515
|
+
return [];
|
|
516
|
+
}
|
|
517
|
+
const callFrames = params.callFrames.filter(isCDPCallFrame);
|
|
518
|
+
if (!this.wasmScriptId) {
|
|
519
|
+
this.log(`getPausedWasmCallFrames skipped ${callFrames.length} non-wasm startup frames before wasm script load`);
|
|
520
|
+
return [];
|
|
521
|
+
}
|
|
522
|
+
const wasmCallFrames = callFrames.filter((callFrame) => callFrame.location.scriptId === this.wasmScriptId);
|
|
523
|
+
this.log(`getPausedWasmCallFrames total=${callFrames.length} wasm=${wasmCallFrames.length}`);
|
|
524
|
+
return wasmCallFrames;
|
|
525
|
+
}
|
|
526
|
+
isTopFrameWasm(params) {
|
|
527
|
+
if (!Array.isArray(params?.callFrames) || !this.wasmScriptId) {
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
const topCallFrame = params.callFrames.find(isCDPCallFrame);
|
|
531
|
+
return topCallFrame?.location.scriptId === this.wasmScriptId;
|
|
532
|
+
}
|
|
533
|
+
toPausedWasmFrame(callFrame) {
|
|
534
|
+
return {
|
|
535
|
+
functionName: callFrame.functionName,
|
|
536
|
+
wasmBytecodeOffset: this.resolveWasmLocationBytecodeOffset(callFrame.location),
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
async getLocalScopeVariables(callFrame) {
|
|
540
|
+
const localScope = callFrame.scopeChain.find((scope) => scope.type === "local") ?? callFrame.scopeChain[0];
|
|
541
|
+
const objectId = localScope?.object.objectId;
|
|
542
|
+
if (!objectId) {
|
|
543
|
+
return [];
|
|
544
|
+
}
|
|
545
|
+
const result = await this.waitForCommandResult("Runtime.getProperties", {
|
|
546
|
+
objectId,
|
|
547
|
+
ownProperties: true,
|
|
548
|
+
});
|
|
549
|
+
const properties = Array.isArray(result.result) ? result.result.filter(isCDPPropertyDescriptor) : [];
|
|
550
|
+
const variables = [];
|
|
551
|
+
for (const property of properties) {
|
|
552
|
+
if (!property.value) {
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
const localIndex = variables.length;
|
|
556
|
+
variables.push({
|
|
557
|
+
name: `local${localIndex}`,
|
|
558
|
+
value: await this.readRemoteObjectValue(property.value),
|
|
559
|
+
type: property.value.description ?? property.value.type,
|
|
560
|
+
localIndex,
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
return variables;
|
|
564
|
+
}
|
|
565
|
+
async getWasmMemoryBufferObjectId() {
|
|
566
|
+
if (this.wasmMemoryBufferObjectId) {
|
|
567
|
+
return this.wasmMemoryBufferObjectId;
|
|
568
|
+
}
|
|
569
|
+
this.log("getWasmMemoryBufferObjectId -> getWasmMemoryObjectId");
|
|
570
|
+
const memoryObjectId = await this.getWasmMemoryObjectId();
|
|
571
|
+
if (!memoryObjectId) {
|
|
572
|
+
this.log("getWasmMemoryBufferObjectId failed: memory object unavailable");
|
|
573
|
+
return undefined;
|
|
574
|
+
}
|
|
575
|
+
this.log("getWasmMemoryBufferObjectId -> Runtime.callFunctionOn buffer");
|
|
576
|
+
const bufferResult = await this.waitForCommandResult("Runtime.callFunctionOn", {
|
|
577
|
+
objectId: memoryObjectId,
|
|
578
|
+
functionDeclaration: GET_WASM_MEMORY_BUFFER_FUNCTION,
|
|
579
|
+
silent: true,
|
|
580
|
+
objectGroup: LINEAR_MEMORY_OBJECT_GROUP,
|
|
581
|
+
});
|
|
582
|
+
const bufferObject = getRuntimeEvaluateRemoteObject(bufferResult);
|
|
583
|
+
this.wasmMemoryBufferObjectId = bufferObject?.objectId;
|
|
584
|
+
this.log(`getWasmMemoryBufferObjectId result=${this.wasmMemoryBufferObjectId ?? "undefined"}`);
|
|
585
|
+
return this.wasmMemoryBufferObjectId;
|
|
586
|
+
}
|
|
587
|
+
async getWasmMemoryObjectId() {
|
|
588
|
+
const moduleScope = this.pausedCallFrames[0]?.scopeChain.find((scope) => scope.type === "module");
|
|
589
|
+
const objectId = moduleScope?.object.objectId;
|
|
590
|
+
if (!objectId) {
|
|
591
|
+
return undefined;
|
|
592
|
+
}
|
|
593
|
+
this.log("getWasmMemoryObjectId -> Runtime.getProperties module scope");
|
|
594
|
+
const result = await this.waitForCommandResult("Runtime.getProperties", {
|
|
595
|
+
objectId,
|
|
596
|
+
ownProperties: false,
|
|
597
|
+
});
|
|
598
|
+
const properties = Array.isArray(result.result) ? result.result.filter(isCDPPropertyDescriptor) : [];
|
|
599
|
+
const memoriesProperty = properties.find((property) => property.name === "memories" && property.value?.objectId);
|
|
600
|
+
if (!memoriesProperty?.value?.objectId) {
|
|
601
|
+
return undefined;
|
|
602
|
+
}
|
|
603
|
+
this.log("getWasmMemoryObjectId -> Runtime.getProperties memories");
|
|
604
|
+
const memoriesResult = await this.waitForCommandResult("Runtime.getProperties", {
|
|
605
|
+
objectId: memoriesProperty.value.objectId,
|
|
606
|
+
ownProperties: false,
|
|
607
|
+
});
|
|
608
|
+
const memories = Array.isArray(memoriesResult.result) ? memoriesResult.result.filter(isCDPPropertyDescriptor) : [];
|
|
609
|
+
const nestedMemory = memories.find((property) => property.value && isWasmMemoryObject(property.value));
|
|
610
|
+
this.log(`getWasmMemoryObjectId result=${nestedMemory?.value?.objectId ?? "undefined"}`);
|
|
611
|
+
return nestedMemory?.value?.objectId;
|
|
612
|
+
}
|
|
613
|
+
async getWasmGlobalsObjectId() {
|
|
614
|
+
if (this.wasmGlobalsObjectId) {
|
|
615
|
+
return this.wasmGlobalsObjectId;
|
|
616
|
+
}
|
|
617
|
+
const moduleScope = this.pausedCallFrames[0]?.scopeChain.find((scope) => scope.type === "module");
|
|
618
|
+
const moduleObjectId = moduleScope?.object.objectId;
|
|
619
|
+
if (!moduleObjectId) {
|
|
620
|
+
return undefined;
|
|
621
|
+
}
|
|
622
|
+
const moduleResult = await this.waitForCommandResult("Runtime.getProperties", {
|
|
623
|
+
objectId: moduleObjectId,
|
|
624
|
+
ownProperties: false,
|
|
625
|
+
});
|
|
626
|
+
const moduleProperties = Array.isArray(moduleResult.result)
|
|
627
|
+
? moduleResult.result.filter(isCDPPropertyDescriptor)
|
|
628
|
+
: [];
|
|
629
|
+
this.wasmGlobalsObjectId = moduleProperties.find((property) => property.name === "globals")?.value?.objectId;
|
|
630
|
+
return this.wasmGlobalsObjectId;
|
|
631
|
+
}
|
|
632
|
+
async readRemoteObjectValue(value) {
|
|
633
|
+
if (value.subtype !== "wasmvalue" || !value.objectId) {
|
|
634
|
+
return formatRemoteObject(value);
|
|
635
|
+
}
|
|
636
|
+
const result = await this.waitForCommandResult("Runtime.getProperties", {
|
|
637
|
+
objectId: value.objectId,
|
|
638
|
+
ownProperties: true,
|
|
639
|
+
});
|
|
640
|
+
const innerProperties = Array.isArray(result.result) ? result.result.filter(isCDPPropertyDescriptor) : [];
|
|
641
|
+
const valueProperty = innerProperties.find((property) => property.name === "value" || property.name === "0");
|
|
642
|
+
return valueProperty?.value ? formatRemoteObject(valueProperty.value) : formatRemoteObject(value);
|
|
643
|
+
}
|
|
232
644
|
async handleModuleLoad(runtimeInfo) {
|
|
645
|
+
if (this.hasRuntimeExited()) {
|
|
646
|
+
this.log("skip wasm module load because runtime already exited");
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
233
649
|
if (!this.wasmFilePath) {
|
|
234
650
|
throw new Error("wasm file path is not available");
|
|
235
651
|
}
|
|
652
|
+
this.log(`loading wasm debug metadata from ${this.wasmFilePath}`);
|
|
236
653
|
const module = await DebuggerWasmModule.load(this.wasmFilePath, runtimeInfo);
|
|
654
|
+
this.log(`wasm debug metadata loaded from ${module.sourceMapFilePath}`);
|
|
237
655
|
void this.onModuleLoad?.(module);
|
|
238
656
|
}
|
|
657
|
+
hasRuntimeExited() {
|
|
658
|
+
return this.child !== undefined && (this.child.exitCode !== null || this.child.signalCode !== null);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
function parseRuntimeStderr(text) {
|
|
662
|
+
let waitingForDebuggerDisconnect = false;
|
|
663
|
+
const output = text
|
|
664
|
+
.split(/(?<=\n)/)
|
|
665
|
+
.filter((line) => {
|
|
666
|
+
const trimmed = line.trim();
|
|
667
|
+
if (trimmed === NODE_WAITING_FOR_DEBUGGER_DISCONNECT) {
|
|
668
|
+
waitingForDebuggerDisconnect = true;
|
|
669
|
+
return false;
|
|
670
|
+
}
|
|
671
|
+
return (trimmed !== "" &&
|
|
672
|
+
!trimmed.startsWith("Debugger listening on") &&
|
|
673
|
+
!trimmed.startsWith("For help, see:") &&
|
|
674
|
+
trimmed !== "Debugger attached.");
|
|
675
|
+
})
|
|
676
|
+
.join("");
|
|
677
|
+
return { output, waitingForDebuggerDisconnect };
|
|
678
|
+
}
|
|
679
|
+
function getRuntimeEnv() {
|
|
680
|
+
const env = { ...process.env };
|
|
681
|
+
delete env.VSCODE_INSPECTOR_OPTIONS;
|
|
682
|
+
const nodeOptions = filterAutoAttachNodeOptions(env.NODE_OPTIONS);
|
|
683
|
+
if (nodeOptions) {
|
|
684
|
+
env.NODE_OPTIONS = nodeOptions;
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
687
|
+
delete env.NODE_OPTIONS;
|
|
688
|
+
}
|
|
689
|
+
return env;
|
|
690
|
+
}
|
|
691
|
+
function filterAutoAttachNodeOptions(nodeOptions) {
|
|
692
|
+
if (!nodeOptions) {
|
|
693
|
+
return "";
|
|
694
|
+
}
|
|
695
|
+
const parts = nodeOptions.split(/\s+/).filter(Boolean);
|
|
696
|
+
const filtered = [];
|
|
697
|
+
for (let i = 0; i < parts.length; i++) {
|
|
698
|
+
const part = parts[i];
|
|
699
|
+
const next = parts[i + 1];
|
|
700
|
+
if ((part === "--require" || part === "-r") && next && isVsCodeJsDebugBootloader(next)) {
|
|
701
|
+
i++;
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
if ((part.startsWith("--require=") || part.startsWith("-r=")) && isVsCodeJsDebugBootloader(part)) {
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
filtered.push(part);
|
|
708
|
+
}
|
|
709
|
+
return filtered.join(" ");
|
|
710
|
+
}
|
|
711
|
+
function isVsCodeJsDebugBootloader(value) {
|
|
712
|
+
return value.includes("js-debug") || value.includes("vscode-js-debug");
|
|
713
|
+
}
|
|
714
|
+
function isCDPPropertyDescriptor(value) {
|
|
715
|
+
if (!value || typeof value !== "object") {
|
|
716
|
+
return false;
|
|
717
|
+
}
|
|
718
|
+
return typeof value.name === "string";
|
|
719
|
+
}
|
|
720
|
+
function isCDPCallFrame(value) {
|
|
721
|
+
if (typeof value !== "object" || value === null) {
|
|
722
|
+
return false;
|
|
723
|
+
}
|
|
724
|
+
const callFrame = value;
|
|
725
|
+
return (typeof callFrame.callFrameId === "string" &&
|
|
726
|
+
typeof callFrame.functionName === "string" &&
|
|
727
|
+
isCDPLocation(callFrame.location) &&
|
|
728
|
+
Array.isArray(callFrame.scopeChain));
|
|
729
|
+
}
|
|
730
|
+
function isCDPLocation(value) {
|
|
731
|
+
if (typeof value !== "object" || value === null) {
|
|
732
|
+
return false;
|
|
733
|
+
}
|
|
734
|
+
const location = value;
|
|
735
|
+
return (typeof location.scriptId === "string" &&
|
|
736
|
+
typeof location.lineNumber === "number" &&
|
|
737
|
+
typeof location.columnNumber === "number");
|
|
738
|
+
}
|
|
739
|
+
function isWasmMemoryObject(value) {
|
|
740
|
+
return value.objectId !== undefined && value.subtype === "webassemblymemory";
|
|
741
|
+
}
|
|
742
|
+
function getRuntimeEvaluateRemoteObject(result) {
|
|
743
|
+
const remoteObject = result.result;
|
|
744
|
+
if (!remoteObject || typeof remoteObject !== "object") {
|
|
745
|
+
return undefined;
|
|
746
|
+
}
|
|
747
|
+
return remoteObject;
|
|
748
|
+
}
|
|
749
|
+
function formatRemoteObject(value) {
|
|
750
|
+
if (value.unserializableValue !== undefined) {
|
|
751
|
+
return value.unserializableValue;
|
|
752
|
+
}
|
|
753
|
+
const rawValue = value.value;
|
|
754
|
+
if (rawValue !== undefined) {
|
|
755
|
+
if (rawValue === null) {
|
|
756
|
+
return "null";
|
|
757
|
+
}
|
|
758
|
+
if (typeof rawValue === "object") {
|
|
759
|
+
return value.description ?? value.type ?? "<unavailable>";
|
|
760
|
+
}
|
|
761
|
+
if (typeof rawValue === "string") {
|
|
762
|
+
return rawValue;
|
|
763
|
+
}
|
|
764
|
+
if (typeof rawValue === "number" || typeof rawValue === "boolean" || typeof rawValue === "bigint") {
|
|
765
|
+
return rawValue.toString();
|
|
766
|
+
}
|
|
767
|
+
return value.description ?? value.type ?? "<unavailable>";
|
|
768
|
+
}
|
|
769
|
+
return value.description ?? value.type ?? "<unavailable>";
|
|
239
770
|
}
|
|
240
771
|
//# sourceMappingURL=nodeDebugger.js.map
|