warpo 3.1.2 → 3.2.0
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/asconfig.schema.json +10 -2
- package/dist/common/leb128.d.ts +5 -0
- package/dist/common/leb128.js +17 -0
- package/dist/common/leb128.js.map +1 -0
- package/dist/debug_server/debugSession.d.ts +31 -2
- package/dist/debug_server/debugSession.js +491 -126
- package/dist/debug_server/debugSession.js.map +1 -1
- package/dist/debug_server/debugTrace.d.ts +2 -0
- package/dist/debug_server/debugTrace.js +38 -0
- package/dist/debug_server/debugTrace.js.map +1 -0
- package/dist/debug_server/debugger.d.ts +6 -2
- package/dist/debug_server/debuggerWasmModule.d.ts +7 -0
- package/dist/debug_server/debuggerWasmModule.js +162 -17
- package/dist/debug_server/debuggerWasmModule.js.map +1 -1
- package/dist/debug_server/nodeDebugger.d.ts +18 -2
- package/dist/debug_server/nodeDebugger.js +134 -30
- package/dist/debug_server/nodeDebugger.js.map +1 -1
- package/dist/debug_server/tests/debugSession.test.js +598 -21
- package/dist/debug_server/tests/debugSession.test.js.map +1 -1
- package/dist/debug_server/tests/testModule/debugger_breakpoint_update.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_breakpoint_update.js +10 -0
- package/dist/debug_server/tests/testModule/debugger_breakpoint_update.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_breakpoint_validation.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_breakpoint_validation.js +12 -0
- package/dist/debug_server/tests/testModule/debugger_breakpoint_validation.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_function_closure.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_function_closure.js +24 -0
- package/dist/debug_server/tests/testModule/debugger_function_closure.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_generic_function.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_generic_function.js +10 -0
- package/dist/debug_server/tests/testModule/debugger_generic_function.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_import.case.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_import.case.js +9 -0
- package/dist/debug_server/tests/testModule/debugger_import.case.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_import.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_import.js +6 -0
- package/dist/debug_server/tests/testModule/debugger_import.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_member_function.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_member_function.js +16 -0
- package/dist/debug_server/tests/testModule/debugger_member_function.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_pause.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_pause.js +9 -0
- package/dist/debug_server/tests/testModule/debugger_pause.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_stepping.d.ts +3 -0
- package/dist/debug_server/tests/testModule/debugger_stepping.js +28 -0
- package/dist/debug_server/tests/testModule/debugger_stepping.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_typed_array.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_typed_array.js +52 -0
- package/dist/debug_server/tests/testModule/debugger_typed_array.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_utf8_const_str.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_utf8_const_str.js +10 -0
- package/dist/debug_server/tests/testModule/debugger_utf8_const_str.js.map +1 -0
- package/dist/dwarf/dwarfParser.js +6 -13
- package/dist/dwarf/dwarfParser.js.map +1 -1
- package/dist/dwarf/functionDebugInfo.d.ts +3 -0
- package/dist/dwarf/functionDebugInfo.js +24 -0
- package/dist/dwarf/functionDebugInfo.js.map +1 -1
- package/dist/test_runner/core/compiler.js +2 -0
- package/dist/test_runner/core/compiler.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Copyright (C) 2025 wasm-ecosystem
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
import { LoggingDebugSession, InitializedEvent, Thread, Breakpoint, Source, Scope, StackFrame, LoadedSourceEvent, OutputEvent, StoppedEvent, ContinuedEvent, TerminatedEvent, logger, } from "@vscode/debugadapter";
|
|
3
|
+
import { LoggingDebugSession, InitializedEvent, Thread, Breakpoint, BreakpointEvent, Source, Scope, StackFrame, LoadedSourceEvent, OutputEvent, StoppedEvent, ContinuedEvent, TerminatedEvent, logger, } from "@vscode/debugadapter";
|
|
4
4
|
import assert from "node:assert/strict";
|
|
5
5
|
import { existsSync } from "node:fs";
|
|
6
6
|
import * as path from "node:path";
|
|
@@ -9,6 +9,14 @@ import { ARRAY_DATA_START_OFFSET, ARRAY_LENGTH_OFFSET, AS_STRING_CLASS_NAME, OBJ
|
|
|
9
9
|
import { normalizeDebugPath } from "./debugPath.js";
|
|
10
10
|
import { NodeDebugger } from "./nodeDebugger.js";
|
|
11
11
|
import { loadConfig } from "../test_runner/config.js";
|
|
12
|
+
import { configureTrace, trace } from "./debugTrace.js";
|
|
13
|
+
var StepMode;
|
|
14
|
+
(function (StepMode) {
|
|
15
|
+
StepMode[StepMode["None"] = 0] = "None";
|
|
16
|
+
StepMode[StepMode["Into"] = 1] = "Into";
|
|
17
|
+
StepMode[StepMode["Over"] = 2] = "Over";
|
|
18
|
+
StepMode[StepMode["Out"] = 3] = "Out";
|
|
19
|
+
})(StepMode || (StepMode = {}));
|
|
12
20
|
function formatUnknownError(error) {
|
|
13
21
|
if (error instanceof Error) {
|
|
14
22
|
return error.message;
|
|
@@ -18,6 +26,23 @@ function formatUnknownError(error) {
|
|
|
18
26
|
}
|
|
19
27
|
return "unknown error";
|
|
20
28
|
}
|
|
29
|
+
const UTF8_CONST_STR_CLASS_NAME = "~lib/warpo/utf8/const_str/ConstStr";
|
|
30
|
+
function isDisplayableStringClass(typeName) {
|
|
31
|
+
return typeName === AS_STRING_CLASS_NAME || typeName === UTF8_CONST_STR_CLASS_NAME;
|
|
32
|
+
}
|
|
33
|
+
const TYPED_ARRAY_ELEMENT_TYPES = new Map([
|
|
34
|
+
["~lib/typedarray/Int8Array", "i8"],
|
|
35
|
+
["~lib/typedarray/Uint8Array", "u8"],
|
|
36
|
+
["~lib/typedarray/Uint8ClampedArray", "u8"],
|
|
37
|
+
["~lib/typedarray/Int16Array", "i16"],
|
|
38
|
+
["~lib/typedarray/Uint16Array", "u16"],
|
|
39
|
+
["~lib/typedarray/Int32Array", "i32"],
|
|
40
|
+
["~lib/typedarray/Uint32Array", "u32"],
|
|
41
|
+
["~lib/typedarray/Int64Array", "i64"],
|
|
42
|
+
["~lib/typedarray/Uint64Array", "u64"],
|
|
43
|
+
["~lib/typedarray/Float32Array", "f32"],
|
|
44
|
+
["~lib/typedarray/Float64Array", "f64"],
|
|
45
|
+
]);
|
|
21
46
|
async function resolveUnitTestWasmPath(cwd) {
|
|
22
47
|
const configPath = path.join(cwd, "as-test.config.js");
|
|
23
48
|
let outputFolder = "coverage";
|
|
@@ -33,17 +58,21 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
33
58
|
static threadId = 1;
|
|
34
59
|
breakpointId = 0;
|
|
35
60
|
requestedBreakpointsBySource = new Map();
|
|
36
|
-
|
|
61
|
+
pendingBreakpointSources = new Set();
|
|
37
62
|
variableContainers = new Map();
|
|
38
63
|
pausedStackFramesById = new Map();
|
|
39
64
|
nextVariablesReference = 1;
|
|
40
65
|
loadedModule;
|
|
41
66
|
runtime;
|
|
67
|
+
configurationDone = false;
|
|
42
68
|
stoppedWasmBytecodeOffset;
|
|
43
69
|
stoppedForTrap = false;
|
|
70
|
+
stepMode = StepMode.None;
|
|
71
|
+
stepStartSourceLocation;
|
|
44
72
|
debugSessionLogging = false;
|
|
45
73
|
log(msg) {
|
|
46
74
|
logger.log(msg);
|
|
75
|
+
trace("debug-session", msg);
|
|
47
76
|
if (this.debugSessionLogging) {
|
|
48
77
|
this.sendEvent(new OutputEvent(`${msg}\n`, "stdout"));
|
|
49
78
|
}
|
|
@@ -72,12 +101,24 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
72
101
|
}
|
|
73
102
|
this.applyPendingBreakpointUpdates(runtime, () => {
|
|
74
103
|
if (info.reason === "other") {
|
|
104
|
+
if (this.stepMode !== StepMode.None) {
|
|
105
|
+
void this.handleStepPause(runtime, info);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
75
108
|
void runtime.resume().catch((error) => {
|
|
76
109
|
const message = error instanceof Error ? error.message : "unknown error";
|
|
77
110
|
this.log(`Failed to resume after breakpoint update: ${message}`);
|
|
78
111
|
});
|
|
79
112
|
return;
|
|
80
113
|
}
|
|
114
|
+
if (info.reason === "breakpoint" || info.reason === "exception") {
|
|
115
|
+
this.handleNonStepPause(runtime, info);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (this.stepMode !== StepMode.None) {
|
|
119
|
+
void this.handleStepPause(runtime, info);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
81
122
|
this.sendStoppedEvent(runtime, info);
|
|
82
123
|
});
|
|
83
124
|
}
|
|
@@ -88,41 +129,76 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
88
129
|
response.body.supportsBreakpointLocationsRequest = false;
|
|
89
130
|
response.body.supportsLoadedSourcesRequest = true;
|
|
90
131
|
response.body.supportsEvaluateForHovers = false;
|
|
132
|
+
response.body.supportsTerminateRequest = true;
|
|
133
|
+
response.body.supportsRestartRequest = true;
|
|
91
134
|
this.sendResponse(response);
|
|
92
135
|
this.sendEvent(new InitializedEvent());
|
|
93
136
|
}
|
|
94
137
|
setBreakPointsRequest(response, args) {
|
|
95
138
|
const sourcePath = normalizeDebugPath(args.source.path || "");
|
|
96
139
|
const clientLines = args.breakpoints || [];
|
|
97
|
-
const
|
|
140
|
+
const wasmModule = this.loadedModule;
|
|
141
|
+
this.log(`SetBreakpoints request: source=${sourcePath}, lines=${clientLines.map((breakpoint) => breakpoint.line).join(",") || "none"}`);
|
|
142
|
+
const bps = [];
|
|
143
|
+
for (const bp of clientLines) {
|
|
98
144
|
const id = ++this.breakpointId;
|
|
99
|
-
|
|
145
|
+
bps.push({
|
|
100
146
|
id,
|
|
101
147
|
line: bp.line,
|
|
102
|
-
verified:
|
|
148
|
+
verified: false,
|
|
103
149
|
source: sourcePath,
|
|
104
|
-
};
|
|
150
|
+
});
|
|
105
151
|
this.log(`Breakpoint set: ${path.basename(sourcePath)}:${bp.line}`);
|
|
106
|
-
|
|
107
|
-
|
|
152
|
+
}
|
|
153
|
+
if (wasmModule !== undefined) {
|
|
154
|
+
for (const breakpoint of bps) {
|
|
155
|
+
breakpoint.verified = wasmModule.resolveBreakpointLocations(breakpoint).length > 0;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
108
158
|
this.requestedBreakpointsBySource.set(sourcePath, bps);
|
|
109
|
-
this.
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
});
|
|
159
|
+
this.pendingBreakpointSources.clear();
|
|
160
|
+
for (const pendingSourcePath of this.requestedBreakpointsBySource.keys()) {
|
|
161
|
+
this.pendingBreakpointSources.add(pendingSourcePath);
|
|
162
|
+
}
|
|
163
|
+
const breakpoints = bps.map((bp) => this.toDebugProtocolBreakpoint(bp));
|
|
115
164
|
response.body = { breakpoints };
|
|
116
|
-
this.
|
|
117
|
-
|
|
165
|
+
if (this.runtime?.isPaused() && this.loadedModule) {
|
|
166
|
+
this.requestBreakpointUpdate(() => this.sendResponse(response));
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
this.sendResponse(response);
|
|
170
|
+
this.requestBreakpointUpdate();
|
|
171
|
+
}
|
|
118
172
|
}
|
|
119
173
|
configurationDoneRequest(response, _args) {
|
|
174
|
+
this.configurationDone = true;
|
|
120
175
|
super.configurationDoneRequest(response, _args);
|
|
121
176
|
this.logAllBreakpoints();
|
|
177
|
+
this.maybeStartRuntime();
|
|
122
178
|
}
|
|
123
179
|
launchRequest(response, args) {
|
|
124
180
|
void this.doLaunchRequest(response, args);
|
|
125
181
|
}
|
|
182
|
+
restartRequest(response, _args) {
|
|
183
|
+
const runtime = this.runtime;
|
|
184
|
+
if (!runtime) {
|
|
185
|
+
this.sendErrorResponse(response, 1, "No active runtime to restart");
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
this.log("Restart requested");
|
|
189
|
+
this.resetRuntimeState();
|
|
190
|
+
this.queueAllBreakpointUpdates();
|
|
191
|
+
void this.doRestartRequest(response, runtime);
|
|
192
|
+
}
|
|
193
|
+
async doRestartRequest(response, runtime) {
|
|
194
|
+
try {
|
|
195
|
+
await runtime.restart();
|
|
196
|
+
this.sendResponse(response);
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
this.sendErrorResponse(response, 1, `Restart failed: ${formatUnknownError(error)}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
126
202
|
async doLaunchRequest(response, args) {
|
|
127
203
|
try {
|
|
128
204
|
this.debugSessionLogging = args.debugSessionLogging ?? false;
|
|
@@ -134,6 +210,7 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
134
210
|
return;
|
|
135
211
|
}
|
|
136
212
|
const wasmFilePath = path.resolve(program);
|
|
213
|
+
configureTrace(args.debugSessionLogFile, cwd);
|
|
137
214
|
this.log(`Launch requested for: ${program}`);
|
|
138
215
|
if (launchType === "wasm file" || launchType === "unittest") {
|
|
139
216
|
const runtimeName = args.runtime ?? "node";
|
|
@@ -148,6 +225,7 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
148
225
|
runtime.onModuleLoad = (wasmModule) => {
|
|
149
226
|
this.loadedModule = wasmModule;
|
|
150
227
|
this.log(`Wasm module loaded: ${wasmModule.wasmFilePath} (reported as ${wasmModule.url}, scriptId: ${wasmModule.scriptId})`);
|
|
228
|
+
this.log(`Module load breakpoint state: requestedSources=${this.requestedBreakpointsBySource.size}, pendingSources=${this.pendingBreakpointSources.size}`);
|
|
151
229
|
this.sendEvent(new LoadedSourceEvent("new", {
|
|
152
230
|
name: path.basename(wasmModule.wasmFilePath),
|
|
153
231
|
path: wasmModule.wasmFilePath,
|
|
@@ -156,16 +234,7 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
156
234
|
name: path.basename(wasmModule.sourceMapFilePath),
|
|
157
235
|
path: wasmModule.sourceMapFilePath,
|
|
158
236
|
}));
|
|
159
|
-
this.applyPendingBreakpointUpdates(runtime, () =>
|
|
160
|
-
if (runtime.isPaused()) {
|
|
161
|
-
void runtime.resume().catch((error) => {
|
|
162
|
-
const message = error instanceof Error ? error.message : "unknown error";
|
|
163
|
-
this.log(`Failed to resume after module load: ${message}`);
|
|
164
|
-
});
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
runtime.finishModuleLoad();
|
|
168
|
-
});
|
|
237
|
+
this.applyPendingBreakpointUpdates(runtime, () => this.maybeStartRuntime());
|
|
169
238
|
};
|
|
170
239
|
runtime.onPause = (info) => {
|
|
171
240
|
this.handleRuntimePause(runtime, info);
|
|
@@ -180,7 +249,6 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
180
249
|
this.log(message);
|
|
181
250
|
};
|
|
182
251
|
this.disposeLoadedModule();
|
|
183
|
-
this.runtime?.dispose();
|
|
184
252
|
this.runtime = runtime;
|
|
185
253
|
await runtime.launch(launchType === "unittest"
|
|
186
254
|
? {
|
|
@@ -228,9 +296,52 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
228
296
|
continueRequest(response, _args) {
|
|
229
297
|
this.doContinueRequest(response);
|
|
230
298
|
}
|
|
299
|
+
stepInRequest(response, _args) {
|
|
300
|
+
this.doStepRequest(response, StepMode.Into);
|
|
301
|
+
}
|
|
302
|
+
nextRequest(response, _args) {
|
|
303
|
+
this.doStepRequest(response, StepMode.Over);
|
|
304
|
+
}
|
|
305
|
+
pauseRequest(response, _args) {
|
|
306
|
+
const runtime = this.runtime;
|
|
307
|
+
if (!runtime) {
|
|
308
|
+
this.sendErrorResponse(response, 1, "No active runtime");
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
if (runtime.isPaused()) {
|
|
312
|
+
this.sendResponse(response);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
this.cancelStep();
|
|
316
|
+
// StepMode.Into is used as a sentinel to reuse the step-pause handling path; no actual step-in is performed.
|
|
317
|
+
this.stepMode = StepMode.Into;
|
|
318
|
+
this.stepStartSourceLocation = undefined;
|
|
319
|
+
this.clearVariableContainers();
|
|
320
|
+
runtime.pause();
|
|
321
|
+
this.sendResponse(response);
|
|
322
|
+
}
|
|
323
|
+
terminateRequest(response, _args) {
|
|
324
|
+
const runtime = this.runtime;
|
|
325
|
+
if (runtime) {
|
|
326
|
+
void this.disposeRuntimeAndTerminate(runtime);
|
|
327
|
+
}
|
|
328
|
+
this.sendResponse(response);
|
|
329
|
+
}
|
|
330
|
+
stepOutRequest(response, _args) {
|
|
331
|
+
this.doStepRequest(response, StepMode.Out);
|
|
332
|
+
}
|
|
231
333
|
disconnectRequest(response, _args) {
|
|
232
|
-
this.
|
|
334
|
+
void this.doDisconnectRequest(response);
|
|
335
|
+
}
|
|
336
|
+
async doDisconnectRequest(response) {
|
|
337
|
+
const runtime = this.runtime;
|
|
233
338
|
this.runtime = undefined;
|
|
339
|
+
try {
|
|
340
|
+
await runtime?.dispose();
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
this.log(`Failed to dispose runtime: ${formatUnknownError(error)}`);
|
|
344
|
+
}
|
|
234
345
|
this.resetRuntimeState();
|
|
235
346
|
this.debugSessionLogging = false;
|
|
236
347
|
this.dispose();
|
|
@@ -293,6 +404,16 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
293
404
|
response.body = { variables: await this.decodeObjectAtAddress(variableContainer.address) };
|
|
294
405
|
break;
|
|
295
406
|
}
|
|
407
|
+
case "variables": {
|
|
408
|
+
response.body = { variables: variableContainer.variables };
|
|
409
|
+
break;
|
|
410
|
+
}
|
|
411
|
+
case "closure-scopes": {
|
|
412
|
+
response.body = {
|
|
413
|
+
variables: await this.resolveClosureScopeVariables(variableContainer.functionName, variableContainer.envAddress, variableContainer.scopeLevels),
|
|
414
|
+
};
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
296
417
|
case "map-entry": {
|
|
297
418
|
response.body = {
|
|
298
419
|
variables: [variableContainer.key, variableContainer.value].map((variable) => this.toDebugProtocolVariable(variable)),
|
|
@@ -358,24 +479,78 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
358
479
|
}
|
|
359
480
|
const sourceVariables = sourceVariableScope.variables;
|
|
360
481
|
const rawWasmValues = await this.resolveRawWasmValues(sourceVariables, runtimeVariablesByIndex, runtime);
|
|
361
|
-
const
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
482
|
+
const firstTupleAddress = this.resolveClosureTupleAddress(sourceVariableScope.rootClosureEnvLocalIndex, runtimeVariablesByIndex);
|
|
483
|
+
const decodedScopes = await this.decodeScopeChainVariables([sourceVariableScope], firstTupleAddress, rawWasmValues);
|
|
484
|
+
return decodedScopes[0] ?? [];
|
|
485
|
+
}
|
|
486
|
+
async decodeScopeChainVariables(scopeLevels, firstTupleAddress, rawWasmValues) {
|
|
487
|
+
const closureTupleValuesByLevel = new Map();
|
|
488
|
+
const visitedAddresses = new Set();
|
|
489
|
+
if (firstTupleAddress !== undefined && Number.isInteger(firstTupleAddress) && firstTupleAddress > 0) {
|
|
490
|
+
let currentAddress = firstTupleAddress;
|
|
491
|
+
let tupleLevel = 0;
|
|
492
|
+
while (!visitedAddresses.has(currentAddress)) {
|
|
493
|
+
visitedAddresses.add(currentAddress);
|
|
494
|
+
const tupleValues = await this.readTupleElementValues(currentAddress);
|
|
495
|
+
const levelValues = new Map();
|
|
496
|
+
for (const [index, value] of tupleValues.entries()) {
|
|
497
|
+
levelValues.set(index, value.value);
|
|
498
|
+
}
|
|
499
|
+
closureTupleValuesByLevel.set(tupleLevel, levelValues);
|
|
500
|
+
const parentTuple = tupleValues[0];
|
|
501
|
+
if (!parentTuple) {
|
|
502
|
+
break;
|
|
370
503
|
}
|
|
371
|
-
|
|
504
|
+
const parentAddress = Number(parentTuple.value);
|
|
505
|
+
if (!Number.isInteger(parentAddress) || parentAddress <= 0) {
|
|
506
|
+
break;
|
|
507
|
+
}
|
|
508
|
+
currentAddress = parentAddress;
|
|
509
|
+
tupleLevel++;
|
|
372
510
|
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
511
|
+
}
|
|
512
|
+
return Promise.all(scopeLevels.map(async (scopeLevel) => {
|
|
513
|
+
const rawLocalValues = scopeLevel.variables.map((variable) => {
|
|
514
|
+
if (variable.kind === "closure") {
|
|
515
|
+
const elementIndex = variable.fieldOffset / SMALL_TUPLE_SLOT_SIZE;
|
|
516
|
+
const levelValues = closureTupleValuesByLevel.get(scopeLevel.tupleLevel ?? 0);
|
|
517
|
+
const tupleRawValue = levelValues?.get(elementIndex);
|
|
518
|
+
if (tupleRawValue === undefined) {
|
|
519
|
+
return { name: variable.name, value: "<unavailable>", typeName: variable.typeName };
|
|
520
|
+
}
|
|
521
|
+
return { name: variable.name, value: tupleRawValue, typeName: variable.typeName };
|
|
522
|
+
}
|
|
523
|
+
const wasmRawValue = rawWasmValues.get(variable);
|
|
524
|
+
return {
|
|
525
|
+
name: variable.name,
|
|
526
|
+
value: wasmRawValue ?? "<unavailable>",
|
|
527
|
+
typeName: variable.typeName,
|
|
528
|
+
};
|
|
529
|
+
});
|
|
530
|
+
const variables = await Promise.all(rawLocalValues.map((variable) => this.toDebugSessionVariableValue(variable.name, variable.typeName, variable.value)));
|
|
531
|
+
return variables.map((variable) => this.toDebugProtocolVariable(variable));
|
|
532
|
+
}));
|
|
533
|
+
}
|
|
534
|
+
async resolveClosureScopeVariables(functionName, envAddress, scopeLevels) {
|
|
535
|
+
const wasmModule = this.loadedModule;
|
|
536
|
+
if (!wasmModule) {
|
|
537
|
+
this.log(`Warning: cannot expand closure scopes for ${functionName}: wasm module is not loaded`);
|
|
538
|
+
return [];
|
|
539
|
+
}
|
|
540
|
+
if (!scopeLevels) {
|
|
541
|
+
this.log(`Warning: cannot expand closure scopes for ${functionName}: DWARF function is unavailable`);
|
|
542
|
+
return [];
|
|
543
|
+
}
|
|
544
|
+
const decodedScopeVariables = await this.decodeScopeChainVariables(scopeLevels, envAddress, new Map());
|
|
545
|
+
return scopeLevels.map((scopeLevel, index) => {
|
|
546
|
+
const variable = {
|
|
547
|
+
kind: "synthetic",
|
|
548
|
+
name: index.toString(),
|
|
549
|
+
value: scopeLevel.name,
|
|
550
|
+
container: { kind: "variables", variables: decodedScopeVariables[index] ?? [] },
|
|
551
|
+
};
|
|
552
|
+
return this.toDebugProtocolVariable(variable);
|
|
376
553
|
});
|
|
377
|
-
const variables = await Promise.all(rawLocalValues.map((variable) => this.toDebugSessionVariableValue(variable.name, variable.typeName, variable.value)));
|
|
378
|
-
return variables.map((variable) => this.toDebugProtocolVariable(variable));
|
|
379
554
|
}
|
|
380
555
|
async decodeObjectAtAddress(address) {
|
|
381
556
|
const fields = await this.resolveObjectFields(address);
|
|
@@ -400,39 +575,16 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
400
575
|
}
|
|
401
576
|
return rawWasmValues;
|
|
402
577
|
}
|
|
403
|
-
|
|
404
|
-
const closureTupleValuesByLevel = new Map();
|
|
578
|
+
resolveClosureTupleAddress(closureEnvLocalIndex, runtimeVariablesByIndex) {
|
|
405
579
|
if (closureEnvLocalIndex === undefined) {
|
|
406
|
-
return
|
|
580
|
+
return undefined;
|
|
407
581
|
}
|
|
408
582
|
const closureEnvVariable = runtimeVariablesByIndex.get(closureEnvLocalIndex);
|
|
409
583
|
if (closureEnvVariable === undefined) {
|
|
410
|
-
return
|
|
411
|
-
}
|
|
412
|
-
let address = Number(closureEnvVariable.value);
|
|
413
|
-
if (!Number.isInteger(address) || address <= 0) {
|
|
414
|
-
return closureTupleValuesByLevel;
|
|
415
|
-
}
|
|
416
|
-
let level = 0;
|
|
417
|
-
while (true) {
|
|
418
|
-
const tupleValues = await this.readTupleElementValues(address);
|
|
419
|
-
const levelValues = new Map();
|
|
420
|
-
for (const [index, value] of tupleValues.entries()) {
|
|
421
|
-
levelValues.set(index, value.value);
|
|
422
|
-
}
|
|
423
|
-
closureTupleValuesByLevel.set(level, levelValues);
|
|
424
|
-
const parentAddress = levelValues.get(0);
|
|
425
|
-
if (parentAddress === undefined) {
|
|
426
|
-
break;
|
|
427
|
-
}
|
|
428
|
-
const parentAddressNumber = Number(parentAddress);
|
|
429
|
-
if (!Number.isInteger(parentAddressNumber) || parentAddressNumber <= 0) {
|
|
430
|
-
break;
|
|
431
|
-
}
|
|
432
|
-
address = parentAddressNumber;
|
|
433
|
-
level++;
|
|
584
|
+
return undefined;
|
|
434
585
|
}
|
|
435
|
-
|
|
586
|
+
const address = Number(closureEnvVariable.value);
|
|
587
|
+
return Number.isInteger(address) && address > 0 ? address : undefined;
|
|
436
588
|
}
|
|
437
589
|
resolvePausedStackFrame(frameId) {
|
|
438
590
|
return this.pausedStackFramesById.get(frameId);
|
|
@@ -444,7 +596,7 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
444
596
|
if (typeName !== undefined) {
|
|
445
597
|
const objectAddress = this.parseObjectAddress(typeName, value);
|
|
446
598
|
if (objectAddress) {
|
|
447
|
-
if (typeName
|
|
599
|
+
if (isDisplayableStringClass(typeName)) {
|
|
448
600
|
const stringValue = await this.decodeStringAtAddress(typeName, objectAddress);
|
|
449
601
|
return this.createClassVariable(name, typeName, objectAddress, stringValue);
|
|
450
602
|
}
|
|
@@ -479,12 +631,18 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
479
631
|
case "map-entry": {
|
|
480
632
|
return "";
|
|
481
633
|
}
|
|
634
|
+
case "synthetic": {
|
|
635
|
+
return variable.value;
|
|
636
|
+
}
|
|
482
637
|
}
|
|
483
638
|
}
|
|
484
639
|
getVariableReference(variable, isExpandableClass) {
|
|
485
640
|
if (isExpandableClass && variable.kind === "class") {
|
|
486
641
|
return this.toObjectVariablesReference(variable.address);
|
|
487
642
|
}
|
|
643
|
+
if (variable.kind === "synthetic") {
|
|
644
|
+
return variable.container === undefined ? 0 : this.createVariableContainer(variable.container);
|
|
645
|
+
}
|
|
488
646
|
if (variable.kind === "map-entry") {
|
|
489
647
|
return this.createVariableContainer({ kind: "map-entry", key: variable.key, value: variable.value });
|
|
490
648
|
}
|
|
@@ -494,7 +652,7 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
494
652
|
return { kind: "class", name, typeName, address, displayValue };
|
|
495
653
|
}
|
|
496
654
|
parseObjectAddress(typeName, value) {
|
|
497
|
-
if (!this.loadedModule?.getClassLayout(typeName)) {
|
|
655
|
+
if (typeName !== UTF8_CONST_STR_CLASS_NAME && !this.loadedModule?.getClassLayout(typeName)) {
|
|
498
656
|
return undefined;
|
|
499
657
|
}
|
|
500
658
|
const address = Number(value);
|
|
@@ -591,9 +749,16 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
591
749
|
return [];
|
|
592
750
|
}
|
|
593
751
|
const view = new DataView(memory.buffer, memory.byteOffset, memory.byteLength);
|
|
752
|
+
if (classLayout.builtinKind === BuiltinContainerKind.Function) {
|
|
753
|
+
return this.decodeFunctionElements(view, classLayout);
|
|
754
|
+
}
|
|
594
755
|
if (classLayout.builtinKind === BuiltinContainerKind.ArrayBuffer) {
|
|
595
756
|
return this.decodeArrayBufferElements(view);
|
|
596
757
|
}
|
|
758
|
+
const typedArrayElementType = TYPED_ARRAY_ELEMENT_TYPES.get(classLayout.name);
|
|
759
|
+
if (typedArrayElementType !== undefined) {
|
|
760
|
+
return this.decodeTypedArrayElements(address, view, classLayout, typedArrayElementType);
|
|
761
|
+
}
|
|
597
762
|
if (classLayout.builtinKind === BuiltinContainerKind.Array) {
|
|
598
763
|
return this.decodeArrayElements(address, view, classLayout);
|
|
599
764
|
}
|
|
@@ -608,10 +773,49 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
608
773
|
}
|
|
609
774
|
return Promise.all(classLayout.fields.map((field) => this.decodeFieldVariable(view, field)));
|
|
610
775
|
}
|
|
776
|
+
decodeFunctionElements(view, classLayout) {
|
|
777
|
+
const indexField = classLayout.fields.find((field) => field.name === "_index" || field.name === "index");
|
|
778
|
+
let functionName;
|
|
779
|
+
if (indexField) {
|
|
780
|
+
const indexValue = this.decodeFieldValue(view, indexField);
|
|
781
|
+
const functionIndex = Number(indexValue);
|
|
782
|
+
if (Number.isInteger(functionIndex) && functionIndex >= 0) {
|
|
783
|
+
functionName = this.loadedModule?.getFunctionNameByTableIndex(functionIndex);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
else {
|
|
787
|
+
this.log(`Warning: cannot expand function: index field is unavailable`);
|
|
788
|
+
}
|
|
789
|
+
const envField = classLayout.fields.find((field) => field.name === "_env" || field.name === "env");
|
|
790
|
+
let envAddress;
|
|
791
|
+
if (envField) {
|
|
792
|
+
const envValue = Number(this.decodeFieldValue(view, envField));
|
|
793
|
+
if (Number.isInteger(envValue) && envValue > 0) {
|
|
794
|
+
envAddress = envValue;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
else {
|
|
798
|
+
this.log(`Warning: cannot expand function: env field is unavailable`);
|
|
799
|
+
}
|
|
800
|
+
const closureScopes = functionName === undefined ? undefined : this.loadedModule?.getClosureVariableScopesByFunctionName(functionName);
|
|
801
|
+
return [
|
|
802
|
+
{ kind: "basic", name: "name", value: functionName ?? "<unavailable>", typeName: "string" },
|
|
803
|
+
{
|
|
804
|
+
kind: "synthetic",
|
|
805
|
+
name: "[[scopes]]",
|
|
806
|
+
value: `Scopes[${closureScopes?.length ?? 0}]`,
|
|
807
|
+
typeName: "Scopes",
|
|
808
|
+
container: functionName === undefined || envAddress === undefined
|
|
809
|
+
? undefined
|
|
810
|
+
: { kind: "closure-scopes", functionName, envAddress, scopeLevels: closureScopes },
|
|
811
|
+
},
|
|
812
|
+
];
|
|
813
|
+
}
|
|
611
814
|
async resolveRuntimeObjectPayloadSize(address, classLayout) {
|
|
612
815
|
if (classLayout.builtinKind !== BuiltinContainerKind.SmallTuple &&
|
|
613
816
|
classLayout.builtinKind !== BuiltinContainerKind.StaticArray &&
|
|
614
|
-
classLayout.builtinKind !== BuiltinContainerKind.ArrayBuffer
|
|
817
|
+
classLayout.builtinKind !== BuiltinContainerKind.ArrayBuffer &&
|
|
818
|
+
!TYPED_ARRAY_ELEMENT_TYPES.has(classLayout.name)) {
|
|
615
819
|
return classLayout.byteSize;
|
|
616
820
|
}
|
|
617
821
|
const sizeMemory = await this.runtime?.readWasmMemory(address + OBJECT_RTSIZE_OFFSET, OBJECT_RTSIZE_SIZE);
|
|
@@ -651,7 +855,7 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
651
855
|
this.log(`Error: cannot expand tuple reference at ${elementAddress}: runtime class layout is missing`);
|
|
652
856
|
return { kind: "basic", name, value: elementAddress.toString(), typeName: undefined };
|
|
653
857
|
}
|
|
654
|
-
if (classLayout.name
|
|
858
|
+
if (isDisplayableStringClass(classLayout.name)) {
|
|
655
859
|
const stringValue = await this.decodeStringAtAddress(classLayout.name, elementAddress);
|
|
656
860
|
return this.createClassVariable(name, classLayout.name, elementAddress, stringValue);
|
|
657
861
|
}
|
|
@@ -691,6 +895,32 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
691
895
|
}
|
|
692
896
|
return elements;
|
|
693
897
|
}
|
|
898
|
+
async decodeTypedArrayElements(address, view, classLayout, elementTypeName) {
|
|
899
|
+
const dataStartField = classLayout.fields.find((field) => field.name === "dataStart");
|
|
900
|
+
const byteLengthField = classLayout.fields.find((field) => field.name === "byteLength");
|
|
901
|
+
if (!dataStartField || !byteLengthField) {
|
|
902
|
+
this.log(`Error: cannot expand typed array at ${address}: typed array header is unavailable`);
|
|
903
|
+
return [];
|
|
904
|
+
}
|
|
905
|
+
const dataStart = Number(this.decodeFieldValue(view, dataStartField));
|
|
906
|
+
const byteLength = Number(this.decodeFieldValue(view, byteLengthField));
|
|
907
|
+
const elementSize = resolveArrayElementSize(elementTypeName, false);
|
|
908
|
+
if (!Number.isInteger(dataStart) ||
|
|
909
|
+
dataStart < 0 ||
|
|
910
|
+
!Number.isInteger(byteLength) ||
|
|
911
|
+
byteLength < 0 ||
|
|
912
|
+
byteLength % elementSize !== 0) {
|
|
913
|
+
this.log(`Error: cannot expand typed array at ${address}: typed array data is invalid`);
|
|
914
|
+
return [];
|
|
915
|
+
}
|
|
916
|
+
const elementsMemory = await this.runtime?.readWasmMemory(dataStart, byteLength);
|
|
917
|
+
if (!elementsMemory) {
|
|
918
|
+
this.log(`Error: cannot expand typed array at ${address}: typed array data is unavailable`);
|
|
919
|
+
return [];
|
|
920
|
+
}
|
|
921
|
+
const elementsView = new DataView(elementsMemory.buffer, elementsMemory.byteOffset, elementsMemory.byteLength);
|
|
922
|
+
return this.decodeArrayElementVariables(elementsView, byteLength / elementSize, elementTypeName, elementSize, false);
|
|
923
|
+
}
|
|
694
924
|
async decodeStaticArrayElements(address, view, classLayout) {
|
|
695
925
|
assert.equal(classLayout.builtinKind, BuiltinContainerKind.StaticArray);
|
|
696
926
|
const elementTypeName = classLayout.templateType;
|
|
@@ -849,7 +1079,7 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
849
1079
|
const value = this.decodeFieldValue(view, field);
|
|
850
1080
|
const address = field.isReference ? this.parseObjectAddress(field.typeName, value) : undefined;
|
|
851
1081
|
if (address) {
|
|
852
|
-
if (field.typeName
|
|
1082
|
+
if (isDisplayableStringClass(field.typeName)) {
|
|
853
1083
|
const stringValue = await this.decodeStringAtAddress(field.typeName, address);
|
|
854
1084
|
return this.createClassVariable(field.name, field.typeName, address, stringValue);
|
|
855
1085
|
}
|
|
@@ -931,7 +1161,7 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
931
1161
|
}
|
|
932
1162
|
}
|
|
933
1163
|
async decodeStringAtAddress(typeName, address) {
|
|
934
|
-
assert.
|
|
1164
|
+
assert.ok(isDisplayableStringClass(typeName));
|
|
935
1165
|
if (!this.runtime) {
|
|
936
1166
|
this.log(`Error: cannot decode string at ${address}: runtime is not active`);
|
|
937
1167
|
return "<unavailable>";
|
|
@@ -948,7 +1178,8 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
948
1178
|
this.log(`Error: cannot decode string at ${address}: string payload memory is unavailable`);
|
|
949
1179
|
return "<unavailable>";
|
|
950
1180
|
}
|
|
951
|
-
|
|
1181
|
+
const encoding = typeName === UTF8_CONST_STR_CLASS_NAME ? "utf8" : "utf16le";
|
|
1182
|
+
return JSON.stringify(Buffer.from(stringMemory).toString(encoding));
|
|
952
1183
|
}
|
|
953
1184
|
doContinueRequest(response) {
|
|
954
1185
|
const runtime = this.runtime;
|
|
@@ -957,13 +1188,107 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
957
1188
|
return;
|
|
958
1189
|
}
|
|
959
1190
|
response.body = { allThreadsContinued: true };
|
|
960
|
-
this.sendResponse(response);
|
|
961
1191
|
if (this.stoppedForTrap) {
|
|
1192
|
+
this.sendResponse(response);
|
|
962
1193
|
this.log("Continue requested from wasm trap; terminating debug session");
|
|
963
|
-
this.disposeRuntimeAndTerminate(runtime);
|
|
1194
|
+
void this.disposeRuntimeAndTerminate(runtime);
|
|
964
1195
|
return;
|
|
965
1196
|
}
|
|
1197
|
+
this.cancelStep();
|
|
966
1198
|
void this.resumeRuntime(runtime);
|
|
1199
|
+
this.sendResponse(response);
|
|
1200
|
+
}
|
|
1201
|
+
doStepRequest(response, mode) {
|
|
1202
|
+
const runtime = this.runtime;
|
|
1203
|
+
if (!runtime) {
|
|
1204
|
+
this.sendErrorResponse(response, 1, "No active runtime");
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1207
|
+
if (!runtime.isPaused()) {
|
|
1208
|
+
this.sendErrorResponse(response, 1, "Runtime is not paused");
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
if (this.stoppedForTrap) {
|
|
1212
|
+
this.sendResponse(response);
|
|
1213
|
+
this.log("Step requested from wasm trap; terminating debug session");
|
|
1214
|
+
void this.disposeRuntimeAndTerminate(runtime);
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
this.stepMode = mode;
|
|
1218
|
+
this.stepStartSourceLocation = this.resolveSourceLocation(this.stoppedWasmBytecodeOffset);
|
|
1219
|
+
this.clearVariableContainers();
|
|
1220
|
+
this.sendResponse(response);
|
|
1221
|
+
this.sendEvent(new ContinuedEvent(WarpoDebugSession.threadId, true));
|
|
1222
|
+
void this.advanceStep(runtime).catch((error) => {
|
|
1223
|
+
this.handleStepFailure(error);
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
async handleStepPause(runtime, info) {
|
|
1227
|
+
try {
|
|
1228
|
+
if (this.hasReachedNextSourceLine(info.wasmBytecodeOffset)) {
|
|
1229
|
+
this.completeStep(runtime, info);
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
await this.advanceStep(runtime);
|
|
1233
|
+
}
|
|
1234
|
+
catch (error) {
|
|
1235
|
+
this.handleStepFailure(error);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
handleNonStepPause(runtime, info) {
|
|
1239
|
+
this.cancelStep();
|
|
1240
|
+
if (this.runtime !== runtime) {
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
this.sendStoppedEvent(runtime, info);
|
|
1244
|
+
}
|
|
1245
|
+
hasReachedNextSourceLine(wasmBytecodeOffset) {
|
|
1246
|
+
const sourceLocation = this.resolveSourceLocation(wasmBytecodeOffset);
|
|
1247
|
+
if (!sourceLocation) {
|
|
1248
|
+
return false;
|
|
1249
|
+
}
|
|
1250
|
+
const startSourceLocation = this.stepStartSourceLocation;
|
|
1251
|
+
return (startSourceLocation === undefined ||
|
|
1252
|
+
sourceLocation.sourcePath !== startSourceLocation.sourcePath ||
|
|
1253
|
+
sourceLocation.sourceLine !== startSourceLocation.sourceLine);
|
|
1254
|
+
}
|
|
1255
|
+
async advanceStep(runtime) {
|
|
1256
|
+
if (this.runtime !== runtime || this.stepMode === StepMode.None) {
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
const wasmModule = this.loadedModule;
|
|
1260
|
+
const wasmBytecodeOffset = this.stoppedWasmBytecodeOffset;
|
|
1261
|
+
if (this.stepMode === StepMode.Out) {
|
|
1262
|
+
this.stepMode = StepMode.Into;
|
|
1263
|
+
await runtime.stepOut();
|
|
1264
|
+
}
|
|
1265
|
+
else if (this.stepMode === StepMode.Over &&
|
|
1266
|
+
wasmModule &&
|
|
1267
|
+
wasmBytecodeOffset !== undefined &&
|
|
1268
|
+
wasmModule.getNextBytecodeOffsetAfterCall(wasmBytecodeOffset) !== undefined) {
|
|
1269
|
+
await runtime.stepOver();
|
|
1270
|
+
}
|
|
1271
|
+
else {
|
|
1272
|
+
// Source-level stepping advances through runtime primitives until the source location changes. Calls use the
|
|
1273
|
+
// runtime's call-aware operation because their following bytecode may be a structural `end`, not an executable instruction.
|
|
1274
|
+
await runtime.stepInstruction();
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
completeStep(runtime, info) {
|
|
1278
|
+
this.cancelStep();
|
|
1279
|
+
if (this.runtime !== runtime) {
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
this.clearVariableContainers();
|
|
1283
|
+
this.sendStoppedEvent(runtime, info);
|
|
1284
|
+
}
|
|
1285
|
+
handleStepFailure(error) {
|
|
1286
|
+
this.log(`Step failed: ${formatUnknownError(error)}`);
|
|
1287
|
+
this.cancelStep();
|
|
1288
|
+
}
|
|
1289
|
+
cancelStep() {
|
|
1290
|
+
this.stepMode = StepMode.None;
|
|
1291
|
+
this.stepStartSourceLocation = undefined;
|
|
967
1292
|
}
|
|
968
1293
|
async resumeRuntime(runtime) {
|
|
969
1294
|
try {
|
|
@@ -984,21 +1309,33 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
984
1309
|
if (this.runtime !== runtime) {
|
|
985
1310
|
return;
|
|
986
1311
|
}
|
|
987
|
-
this.disposeRuntimeAndTerminate(runtime);
|
|
1312
|
+
void this.disposeRuntimeAndTerminate(runtime);
|
|
988
1313
|
}
|
|
989
|
-
disposeRuntimeAndTerminate(runtime) {
|
|
990
|
-
runtime.dispose();
|
|
1314
|
+
async disposeRuntimeAndTerminate(runtime) {
|
|
991
1315
|
if (this.runtime === runtime) {
|
|
992
1316
|
this.runtime = undefined;
|
|
993
1317
|
}
|
|
1318
|
+
try {
|
|
1319
|
+
await runtime.dispose();
|
|
1320
|
+
}
|
|
1321
|
+
catch (error) {
|
|
1322
|
+
this.log(`Failed to dispose runtime: ${formatUnknownError(error)}`);
|
|
1323
|
+
}
|
|
994
1324
|
this.resetRuntimeState();
|
|
995
1325
|
this.sendEvent(new TerminatedEvent());
|
|
996
1326
|
}
|
|
997
1327
|
resetRuntimeState() {
|
|
998
1328
|
this.disposeLoadedModule();
|
|
999
|
-
this.
|
|
1329
|
+
this.pendingBreakpointSources.clear();
|
|
1000
1330
|
this.stoppedWasmBytecodeOffset = undefined;
|
|
1001
1331
|
this.stoppedForTrap = false;
|
|
1332
|
+
this.stepMode = StepMode.None;
|
|
1333
|
+
this.stepStartSourceLocation = undefined;
|
|
1334
|
+
}
|
|
1335
|
+
queueAllBreakpointUpdates() {
|
|
1336
|
+
for (const sourcePath of this.requestedBreakpointsBySource.keys()) {
|
|
1337
|
+
this.pendingBreakpointSources.add(sourcePath);
|
|
1338
|
+
}
|
|
1002
1339
|
}
|
|
1003
1340
|
logAllBreakpoints() {
|
|
1004
1341
|
this.log("=== Registered Breakpoints ===");
|
|
@@ -1009,63 +1346,91 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
1009
1346
|
}
|
|
1010
1347
|
this.log("==============================");
|
|
1011
1348
|
}
|
|
1012
|
-
|
|
1013
|
-
|
|
1349
|
+
toDebugProtocolBreakpoint(breakpoint) {
|
|
1350
|
+
const dbp = new Breakpoint(breakpoint.verified, breakpoint.line);
|
|
1351
|
+
dbp.id = breakpoint.id;
|
|
1352
|
+
return dbp;
|
|
1353
|
+
}
|
|
1354
|
+
requestBreakpointUpdate(onComplete) {
|
|
1355
|
+
this.log(`Breakpoint update requested: hasRuntime=${this.runtime !== undefined}, hasModule=${this.loadedModule !== undefined}, pendingSources=${this.pendingBreakpointSources.size}, runtimePaused=${this.runtime?.isPaused() ?? false}`);
|
|
1356
|
+
if (!this.runtime || !this.loadedModule || this.pendingBreakpointSources.size === 0) {
|
|
1357
|
+
this.log("Breakpoint update skipped: runtime, module, or pending source is unavailable");
|
|
1358
|
+
onComplete?.();
|
|
1014
1359
|
return;
|
|
1015
1360
|
}
|
|
1016
1361
|
if (this.runtime.isPaused()) {
|
|
1017
|
-
this.applyPendingBreakpointUpdates(this.runtime);
|
|
1362
|
+
this.applyPendingBreakpointUpdates(this.runtime, onComplete);
|
|
1018
1363
|
return;
|
|
1019
1364
|
}
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1365
|
+
this.runtime.pause();
|
|
1366
|
+
}
|
|
1367
|
+
maybeStartRuntime() {
|
|
1368
|
+
const runtime = this.runtime;
|
|
1369
|
+
if (!this.configurationDone || !runtime || !this.loadedModule) {
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
if (runtime.isPaused()) {
|
|
1373
|
+
void runtime.resume().catch((error) => {
|
|
1374
|
+
const message = error instanceof Error ? error.message : "unknown error";
|
|
1375
|
+
this.log(`Failed to resume after configuration: ${message}`);
|
|
1376
|
+
});
|
|
1377
|
+
return;
|
|
1025
1378
|
}
|
|
1379
|
+
runtime.finishModuleLoad();
|
|
1026
1380
|
}
|
|
1027
1381
|
applyPendingBreakpointUpdates(runtime, onComplete) {
|
|
1028
1382
|
const wasmModule = this.loadedModule;
|
|
1029
1383
|
if (!wasmModule) {
|
|
1384
|
+
this.log("Breakpoint update skipped: no loaded wasm module");
|
|
1030
1385
|
onComplete?.();
|
|
1031
1386
|
return;
|
|
1032
1387
|
}
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
continue;
|
|
1037
|
-
}
|
|
1038
|
-
if (!wasmModule.hasSource(sourcePath)) {
|
|
1039
|
-
continue;
|
|
1040
|
-
}
|
|
1041
|
-
const breakpoint = breakpoints.shift();
|
|
1042
|
-
if (!breakpoint) {
|
|
1043
|
-
this.pendingBreakpointUpdatesBySource.delete(sourcePath);
|
|
1044
|
-
continue;
|
|
1045
|
-
}
|
|
1046
|
-
if (breakpoints.length === 0) {
|
|
1047
|
-
this.pendingBreakpointUpdatesBySource.delete(sourcePath);
|
|
1048
|
-
}
|
|
1049
|
-
const breakpointLocation = wasmModule.resolveBreakpointLocation(breakpoint);
|
|
1050
|
-
if (!breakpointLocation) {
|
|
1051
|
-
this.log(`No source-map match for breakpoint ${path.basename(breakpoint.source)}:${breakpoint.line}`);
|
|
1052
|
-
this.applyPendingBreakpointUpdates(runtime, onComplete);
|
|
1053
|
-
return;
|
|
1054
|
-
}
|
|
1055
|
-
this.log(`Resolved breakpoint ${path.basename(breakpoint.source)}:${breakpoint.line} -> ${breakpointLocation.wasmBytecodeOffset}`);
|
|
1056
|
-
runtime.setWasmBreakpoint(wasmModule, breakpointLocation.wasmBytecodeOffset, {
|
|
1057
|
-
onSuccess: () => {
|
|
1058
|
-
this.log(`Breakpoint installed at ${breakpointLocation.wasmBytecodeOffset}`);
|
|
1059
|
-
this.applyPendingBreakpointUpdates(runtime, onComplete);
|
|
1060
|
-
},
|
|
1061
|
-
onError: (error) => {
|
|
1062
|
-
this.log(`Failed to set breakpoint ${path.basename(breakpoint.source)}:${breakpoint.line} at ${breakpointLocation.wasmBytecodeOffset}: ${error.message}`);
|
|
1063
|
-
this.applyPendingBreakpointUpdates(runtime, onComplete);
|
|
1064
|
-
},
|
|
1065
|
-
});
|
|
1388
|
+
if (this.pendingBreakpointSources.size === 0) {
|
|
1389
|
+
this.log("Breakpoint update skipped: no pending breakpoint sources");
|
|
1390
|
+
onComplete?.();
|
|
1066
1391
|
return;
|
|
1067
1392
|
}
|
|
1068
|
-
|
|
1393
|
+
this.log(`Applying breakpoint updates: requestedSources=${this.requestedBreakpointsBySource.size}, pendingSources=${this.pendingBreakpointSources.size}`);
|
|
1394
|
+
this.pendingBreakpointSources.clear();
|
|
1395
|
+
const wasmBytecodeOffsets = this.collectBreakpointOffsets(wasmModule);
|
|
1396
|
+
runtime.setWasmBreakpoints(wasmModule, Array.from(wasmBytecodeOffsets).toSorted((left, right) => left - right), {
|
|
1397
|
+
onSuccess: () => {
|
|
1398
|
+
this.log(`Breakpoint set installed: ${wasmBytecodeOffsets.size} locations, offsets=${Array.from(wasmBytecodeOffsets)
|
|
1399
|
+
.toSorted((left, right) => left - right)
|
|
1400
|
+
.join(",") || "none"}`);
|
|
1401
|
+
onComplete?.();
|
|
1402
|
+
},
|
|
1403
|
+
onError: (error) => {
|
|
1404
|
+
this.log(`Failed to update breakpoints: ${error.message}`);
|
|
1405
|
+
onComplete?.();
|
|
1406
|
+
},
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
1409
|
+
collectBreakpointOffsets(wasmModule) {
|
|
1410
|
+
const wasmBytecodeOffsets = new Set();
|
|
1411
|
+
for (const [sourcePath, breakpoints] of this.requestedBreakpointsBySource) {
|
|
1412
|
+
for (const breakpoint of breakpoints) {
|
|
1413
|
+
this.collectBreakpointLocations(wasmModule, sourcePath, breakpoint, wasmBytecodeOffsets);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
return wasmBytecodeOffsets;
|
|
1417
|
+
}
|
|
1418
|
+
collectBreakpointLocations(wasmModule, sourcePath, breakpoint, wasmBytecodeOffsets) {
|
|
1419
|
+
const sourceMapHasSource = wasmModule.hasSource(sourcePath);
|
|
1420
|
+
this.log(`Resolving breakpoint: source=${sourcePath}, line=${breakpoint.line}, sourceMapHasSource=${sourceMapHasSource}`);
|
|
1421
|
+
const breakpointLocations = wasmModule.resolveBreakpointLocations(breakpoint);
|
|
1422
|
+
if (breakpointLocations.length > 0 && !breakpoint.verified) {
|
|
1423
|
+
breakpoint.verified = true;
|
|
1424
|
+
this.sendEvent(new BreakpointEvent("changed", this.toDebugProtocolBreakpoint(breakpoint)));
|
|
1425
|
+
}
|
|
1426
|
+
if (breakpointLocations.length === 0) {
|
|
1427
|
+
this.log(`Source-map lookup failed for breakpoint ${path.basename(sourcePath)}:${breakpoint.line}: ${sourceMapHasSource ? "source present but line has no mapping" : "source is absent"}`);
|
|
1428
|
+
}
|
|
1429
|
+
this.log(`Breakpoint resolution result: source=${sourcePath}, line=${breakpoint.line}, locations=${breakpointLocations.length}, offsets=${breakpointLocations.map((location) => location.wasmBytecodeOffset).join(",") || "none"}`);
|
|
1430
|
+
for (const location of breakpointLocations) {
|
|
1431
|
+
wasmBytecodeOffsets.add(location.wasmBytecodeOffset);
|
|
1432
|
+
this.log(`Resolved breakpoint ${path.basename(sourcePath)}:${breakpoint.line} -> ${location.wasmBytecodeOffset}`);
|
|
1433
|
+
}
|
|
1069
1434
|
}
|
|
1070
1435
|
disposeLoadedModule() {
|
|
1071
1436
|
this.clearVariableContainers();
|