warpo 3.1.3 → 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/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 +30 -1
- package/dist/debug_server/debugSession.js +462 -93
- 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 +5 -1
- package/dist/debug_server/debuggerWasmModule.d.ts +6 -0
- package/dist/debug_server/debuggerWasmModule.js +135 -2
- package/dist/debug_server/debuggerWasmModule.js.map +1 -1
- package/dist/debug_server/nodeDebugger.d.ts +12 -1
- package/dist/debug_server/nodeDebugger.js +74 -28
- package/dist/debug_server/nodeDebugger.js.map +1 -1
- package/dist/debug_server/tests/debugSession.test.js +460 -22
- package/dist/debug_server/tests/debugSession.test.js.map +1 -1
- 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_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_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/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";
|
|
@@ -39,11 +64,15 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
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,33 +129,38 @@ 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
159
|
this.pendingBreakpointSources.clear();
|
|
110
160
|
for (const pendingSourcePath of this.requestedBreakpointsBySource.keys()) {
|
|
111
161
|
this.pendingBreakpointSources.add(pendingSourcePath);
|
|
112
162
|
}
|
|
113
|
-
const breakpoints = bps.map((bp) =>
|
|
114
|
-
const dbp = new Breakpoint(bp.verified, bp.line);
|
|
115
|
-
dbp.id = bp.id;
|
|
116
|
-
return dbp;
|
|
117
|
-
});
|
|
163
|
+
const breakpoints = bps.map((bp) => this.toDebugProtocolBreakpoint(bp));
|
|
118
164
|
response.body = { breakpoints };
|
|
119
165
|
if (this.runtime?.isPaused() && this.loadedModule) {
|
|
120
166
|
this.requestBreakpointUpdate(() => this.sendResponse(response));
|
|
@@ -125,12 +171,34 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
125
171
|
}
|
|
126
172
|
}
|
|
127
173
|
configurationDoneRequest(response, _args) {
|
|
174
|
+
this.configurationDone = true;
|
|
128
175
|
super.configurationDoneRequest(response, _args);
|
|
129
176
|
this.logAllBreakpoints();
|
|
177
|
+
this.maybeStartRuntime();
|
|
130
178
|
}
|
|
131
179
|
launchRequest(response, args) {
|
|
132
180
|
void this.doLaunchRequest(response, args);
|
|
133
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
|
+
}
|
|
134
202
|
async doLaunchRequest(response, args) {
|
|
135
203
|
try {
|
|
136
204
|
this.debugSessionLogging = args.debugSessionLogging ?? false;
|
|
@@ -142,6 +210,7 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
142
210
|
return;
|
|
143
211
|
}
|
|
144
212
|
const wasmFilePath = path.resolve(program);
|
|
213
|
+
configureTrace(args.debugSessionLogFile, cwd);
|
|
145
214
|
this.log(`Launch requested for: ${program}`);
|
|
146
215
|
if (launchType === "wasm file" || launchType === "unittest") {
|
|
147
216
|
const runtimeName = args.runtime ?? "node";
|
|
@@ -156,6 +225,7 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
156
225
|
runtime.onModuleLoad = (wasmModule) => {
|
|
157
226
|
this.loadedModule = wasmModule;
|
|
158
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}`);
|
|
159
229
|
this.sendEvent(new LoadedSourceEvent("new", {
|
|
160
230
|
name: path.basename(wasmModule.wasmFilePath),
|
|
161
231
|
path: wasmModule.wasmFilePath,
|
|
@@ -164,16 +234,7 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
164
234
|
name: path.basename(wasmModule.sourceMapFilePath),
|
|
165
235
|
path: wasmModule.sourceMapFilePath,
|
|
166
236
|
}));
|
|
167
|
-
this.applyPendingBreakpointUpdates(runtime, () =>
|
|
168
|
-
if (runtime.isPaused()) {
|
|
169
|
-
void runtime.resume().catch((error) => {
|
|
170
|
-
const message = error instanceof Error ? error.message : "unknown error";
|
|
171
|
-
this.log(`Failed to resume after module load: ${message}`);
|
|
172
|
-
});
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
runtime.finishModuleLoad();
|
|
176
|
-
});
|
|
237
|
+
this.applyPendingBreakpointUpdates(runtime, () => this.maybeStartRuntime());
|
|
177
238
|
};
|
|
178
239
|
runtime.onPause = (info) => {
|
|
179
240
|
this.handleRuntimePause(runtime, info);
|
|
@@ -188,7 +249,6 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
188
249
|
this.log(message);
|
|
189
250
|
};
|
|
190
251
|
this.disposeLoadedModule();
|
|
191
|
-
this.runtime?.dispose();
|
|
192
252
|
this.runtime = runtime;
|
|
193
253
|
await runtime.launch(launchType === "unittest"
|
|
194
254
|
? {
|
|
@@ -236,9 +296,52 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
236
296
|
continueRequest(response, _args) {
|
|
237
297
|
this.doContinueRequest(response);
|
|
238
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
|
+
}
|
|
239
333
|
disconnectRequest(response, _args) {
|
|
240
|
-
this.
|
|
334
|
+
void this.doDisconnectRequest(response);
|
|
335
|
+
}
|
|
336
|
+
async doDisconnectRequest(response) {
|
|
337
|
+
const runtime = this.runtime;
|
|
241
338
|
this.runtime = undefined;
|
|
339
|
+
try {
|
|
340
|
+
await runtime?.dispose();
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
this.log(`Failed to dispose runtime: ${formatUnknownError(error)}`);
|
|
344
|
+
}
|
|
242
345
|
this.resetRuntimeState();
|
|
243
346
|
this.debugSessionLogging = false;
|
|
244
347
|
this.dispose();
|
|
@@ -301,6 +404,16 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
301
404
|
response.body = { variables: await this.decodeObjectAtAddress(variableContainer.address) };
|
|
302
405
|
break;
|
|
303
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
|
+
}
|
|
304
417
|
case "map-entry": {
|
|
305
418
|
response.body = {
|
|
306
419
|
variables: [variableContainer.key, variableContainer.value].map((variable) => this.toDebugProtocolVariable(variable)),
|
|
@@ -366,24 +479,78 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
366
479
|
}
|
|
367
480
|
const sourceVariables = sourceVariableScope.variables;
|
|
368
481
|
const rawWasmValues = await this.resolveRawWasmValues(sourceVariables, runtimeVariablesByIndex, runtime);
|
|
369
|
-
const
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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;
|
|
503
|
+
}
|
|
504
|
+
const parentAddress = Number(parentTuple.value);
|
|
505
|
+
if (!Number.isInteger(parentAddress) || parentAddress <= 0) {
|
|
506
|
+
break;
|
|
378
507
|
}
|
|
379
|
-
|
|
508
|
+
currentAddress = parentAddress;
|
|
509
|
+
tupleLevel++;
|
|
380
510
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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);
|
|
384
553
|
});
|
|
385
|
-
const variables = await Promise.all(rawLocalValues.map((variable) => this.toDebugSessionVariableValue(variable.name, variable.typeName, variable.value)));
|
|
386
|
-
return variables.map((variable) => this.toDebugProtocolVariable(variable));
|
|
387
554
|
}
|
|
388
555
|
async decodeObjectAtAddress(address) {
|
|
389
556
|
const fields = await this.resolveObjectFields(address);
|
|
@@ -408,39 +575,16 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
408
575
|
}
|
|
409
576
|
return rawWasmValues;
|
|
410
577
|
}
|
|
411
|
-
|
|
412
|
-
const closureTupleValuesByLevel = new Map();
|
|
578
|
+
resolveClosureTupleAddress(closureEnvLocalIndex, runtimeVariablesByIndex) {
|
|
413
579
|
if (closureEnvLocalIndex === undefined) {
|
|
414
|
-
return
|
|
580
|
+
return undefined;
|
|
415
581
|
}
|
|
416
582
|
const closureEnvVariable = runtimeVariablesByIndex.get(closureEnvLocalIndex);
|
|
417
583
|
if (closureEnvVariable === undefined) {
|
|
418
|
-
return
|
|
419
|
-
}
|
|
420
|
-
let address = Number(closureEnvVariable.value);
|
|
421
|
-
if (!Number.isInteger(address) || address <= 0) {
|
|
422
|
-
return closureTupleValuesByLevel;
|
|
423
|
-
}
|
|
424
|
-
let level = 0;
|
|
425
|
-
while (true) {
|
|
426
|
-
const tupleValues = await this.readTupleElementValues(address);
|
|
427
|
-
const levelValues = new Map();
|
|
428
|
-
for (const [index, value] of tupleValues.entries()) {
|
|
429
|
-
levelValues.set(index, value.value);
|
|
430
|
-
}
|
|
431
|
-
closureTupleValuesByLevel.set(level, levelValues);
|
|
432
|
-
const parentAddress = levelValues.get(0);
|
|
433
|
-
if (parentAddress === undefined) {
|
|
434
|
-
break;
|
|
435
|
-
}
|
|
436
|
-
const parentAddressNumber = Number(parentAddress);
|
|
437
|
-
if (!Number.isInteger(parentAddressNumber) || parentAddressNumber <= 0) {
|
|
438
|
-
break;
|
|
439
|
-
}
|
|
440
|
-
address = parentAddressNumber;
|
|
441
|
-
level++;
|
|
584
|
+
return undefined;
|
|
442
585
|
}
|
|
443
|
-
|
|
586
|
+
const address = Number(closureEnvVariable.value);
|
|
587
|
+
return Number.isInteger(address) && address > 0 ? address : undefined;
|
|
444
588
|
}
|
|
445
589
|
resolvePausedStackFrame(frameId) {
|
|
446
590
|
return this.pausedStackFramesById.get(frameId);
|
|
@@ -452,7 +596,7 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
452
596
|
if (typeName !== undefined) {
|
|
453
597
|
const objectAddress = this.parseObjectAddress(typeName, value);
|
|
454
598
|
if (objectAddress) {
|
|
455
|
-
if (typeName
|
|
599
|
+
if (isDisplayableStringClass(typeName)) {
|
|
456
600
|
const stringValue = await this.decodeStringAtAddress(typeName, objectAddress);
|
|
457
601
|
return this.createClassVariable(name, typeName, objectAddress, stringValue);
|
|
458
602
|
}
|
|
@@ -487,12 +631,18 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
487
631
|
case "map-entry": {
|
|
488
632
|
return "";
|
|
489
633
|
}
|
|
634
|
+
case "synthetic": {
|
|
635
|
+
return variable.value;
|
|
636
|
+
}
|
|
490
637
|
}
|
|
491
638
|
}
|
|
492
639
|
getVariableReference(variable, isExpandableClass) {
|
|
493
640
|
if (isExpandableClass && variable.kind === "class") {
|
|
494
641
|
return this.toObjectVariablesReference(variable.address);
|
|
495
642
|
}
|
|
643
|
+
if (variable.kind === "synthetic") {
|
|
644
|
+
return variable.container === undefined ? 0 : this.createVariableContainer(variable.container);
|
|
645
|
+
}
|
|
496
646
|
if (variable.kind === "map-entry") {
|
|
497
647
|
return this.createVariableContainer({ kind: "map-entry", key: variable.key, value: variable.value });
|
|
498
648
|
}
|
|
@@ -502,7 +652,7 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
502
652
|
return { kind: "class", name, typeName, address, displayValue };
|
|
503
653
|
}
|
|
504
654
|
parseObjectAddress(typeName, value) {
|
|
505
|
-
if (!this.loadedModule?.getClassLayout(typeName)) {
|
|
655
|
+
if (typeName !== UTF8_CONST_STR_CLASS_NAME && !this.loadedModule?.getClassLayout(typeName)) {
|
|
506
656
|
return undefined;
|
|
507
657
|
}
|
|
508
658
|
const address = Number(value);
|
|
@@ -599,9 +749,16 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
599
749
|
return [];
|
|
600
750
|
}
|
|
601
751
|
const view = new DataView(memory.buffer, memory.byteOffset, memory.byteLength);
|
|
752
|
+
if (classLayout.builtinKind === BuiltinContainerKind.Function) {
|
|
753
|
+
return this.decodeFunctionElements(view, classLayout);
|
|
754
|
+
}
|
|
602
755
|
if (classLayout.builtinKind === BuiltinContainerKind.ArrayBuffer) {
|
|
603
756
|
return this.decodeArrayBufferElements(view);
|
|
604
757
|
}
|
|
758
|
+
const typedArrayElementType = TYPED_ARRAY_ELEMENT_TYPES.get(classLayout.name);
|
|
759
|
+
if (typedArrayElementType !== undefined) {
|
|
760
|
+
return this.decodeTypedArrayElements(address, view, classLayout, typedArrayElementType);
|
|
761
|
+
}
|
|
605
762
|
if (classLayout.builtinKind === BuiltinContainerKind.Array) {
|
|
606
763
|
return this.decodeArrayElements(address, view, classLayout);
|
|
607
764
|
}
|
|
@@ -616,10 +773,49 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
616
773
|
}
|
|
617
774
|
return Promise.all(classLayout.fields.map((field) => this.decodeFieldVariable(view, field)));
|
|
618
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
|
+
}
|
|
619
814
|
async resolveRuntimeObjectPayloadSize(address, classLayout) {
|
|
620
815
|
if (classLayout.builtinKind !== BuiltinContainerKind.SmallTuple &&
|
|
621
816
|
classLayout.builtinKind !== BuiltinContainerKind.StaticArray &&
|
|
622
|
-
classLayout.builtinKind !== BuiltinContainerKind.ArrayBuffer
|
|
817
|
+
classLayout.builtinKind !== BuiltinContainerKind.ArrayBuffer &&
|
|
818
|
+
!TYPED_ARRAY_ELEMENT_TYPES.has(classLayout.name)) {
|
|
623
819
|
return classLayout.byteSize;
|
|
624
820
|
}
|
|
625
821
|
const sizeMemory = await this.runtime?.readWasmMemory(address + OBJECT_RTSIZE_OFFSET, OBJECT_RTSIZE_SIZE);
|
|
@@ -659,7 +855,7 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
659
855
|
this.log(`Error: cannot expand tuple reference at ${elementAddress}: runtime class layout is missing`);
|
|
660
856
|
return { kind: "basic", name, value: elementAddress.toString(), typeName: undefined };
|
|
661
857
|
}
|
|
662
|
-
if (classLayout.name
|
|
858
|
+
if (isDisplayableStringClass(classLayout.name)) {
|
|
663
859
|
const stringValue = await this.decodeStringAtAddress(classLayout.name, elementAddress);
|
|
664
860
|
return this.createClassVariable(name, classLayout.name, elementAddress, stringValue);
|
|
665
861
|
}
|
|
@@ -699,6 +895,32 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
699
895
|
}
|
|
700
896
|
return elements;
|
|
701
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
|
+
}
|
|
702
924
|
async decodeStaticArrayElements(address, view, classLayout) {
|
|
703
925
|
assert.equal(classLayout.builtinKind, BuiltinContainerKind.StaticArray);
|
|
704
926
|
const elementTypeName = classLayout.templateType;
|
|
@@ -857,7 +1079,7 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
857
1079
|
const value = this.decodeFieldValue(view, field);
|
|
858
1080
|
const address = field.isReference ? this.parseObjectAddress(field.typeName, value) : undefined;
|
|
859
1081
|
if (address) {
|
|
860
|
-
if (field.typeName
|
|
1082
|
+
if (isDisplayableStringClass(field.typeName)) {
|
|
861
1083
|
const stringValue = await this.decodeStringAtAddress(field.typeName, address);
|
|
862
1084
|
return this.createClassVariable(field.name, field.typeName, address, stringValue);
|
|
863
1085
|
}
|
|
@@ -939,7 +1161,7 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
939
1161
|
}
|
|
940
1162
|
}
|
|
941
1163
|
async decodeStringAtAddress(typeName, address) {
|
|
942
|
-
assert.
|
|
1164
|
+
assert.ok(isDisplayableStringClass(typeName));
|
|
943
1165
|
if (!this.runtime) {
|
|
944
1166
|
this.log(`Error: cannot decode string at ${address}: runtime is not active`);
|
|
945
1167
|
return "<unavailable>";
|
|
@@ -956,7 +1178,8 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
956
1178
|
this.log(`Error: cannot decode string at ${address}: string payload memory is unavailable`);
|
|
957
1179
|
return "<unavailable>";
|
|
958
1180
|
}
|
|
959
|
-
|
|
1181
|
+
const encoding = typeName === UTF8_CONST_STR_CLASS_NAME ? "utf8" : "utf16le";
|
|
1182
|
+
return JSON.stringify(Buffer.from(stringMemory).toString(encoding));
|
|
960
1183
|
}
|
|
961
1184
|
doContinueRequest(response) {
|
|
962
1185
|
const runtime = this.runtime;
|
|
@@ -965,13 +1188,107 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
965
1188
|
return;
|
|
966
1189
|
}
|
|
967
1190
|
response.body = { allThreadsContinued: true };
|
|
968
|
-
this.sendResponse(response);
|
|
969
1191
|
if (this.stoppedForTrap) {
|
|
1192
|
+
this.sendResponse(response);
|
|
970
1193
|
this.log("Continue requested from wasm trap; terminating debug session");
|
|
971
|
-
this.disposeRuntimeAndTerminate(runtime);
|
|
1194
|
+
void this.disposeRuntimeAndTerminate(runtime);
|
|
972
1195
|
return;
|
|
973
1196
|
}
|
|
1197
|
+
this.cancelStep();
|
|
974
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;
|
|
975
1292
|
}
|
|
976
1293
|
async resumeRuntime(runtime) {
|
|
977
1294
|
try {
|
|
@@ -992,13 +1309,18 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
992
1309
|
if (this.runtime !== runtime) {
|
|
993
1310
|
return;
|
|
994
1311
|
}
|
|
995
|
-
this.disposeRuntimeAndTerminate(runtime);
|
|
1312
|
+
void this.disposeRuntimeAndTerminate(runtime);
|
|
996
1313
|
}
|
|
997
|
-
disposeRuntimeAndTerminate(runtime) {
|
|
998
|
-
runtime.dispose();
|
|
1314
|
+
async disposeRuntimeAndTerminate(runtime) {
|
|
999
1315
|
if (this.runtime === runtime) {
|
|
1000
1316
|
this.runtime = undefined;
|
|
1001
1317
|
}
|
|
1318
|
+
try {
|
|
1319
|
+
await runtime.dispose();
|
|
1320
|
+
}
|
|
1321
|
+
catch (error) {
|
|
1322
|
+
this.log(`Failed to dispose runtime: ${formatUnknownError(error)}`);
|
|
1323
|
+
}
|
|
1002
1324
|
this.resetRuntimeState();
|
|
1003
1325
|
this.sendEvent(new TerminatedEvent());
|
|
1004
1326
|
}
|
|
@@ -1007,6 +1329,13 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
1007
1329
|
this.pendingBreakpointSources.clear();
|
|
1008
1330
|
this.stoppedWasmBytecodeOffset = undefined;
|
|
1009
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
|
+
}
|
|
1010
1339
|
}
|
|
1011
1340
|
logAllBreakpoints() {
|
|
1012
1341
|
this.log("=== Registered Breakpoints ===");
|
|
@@ -1017,8 +1346,15 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
1017
1346
|
}
|
|
1018
1347
|
this.log("==============================");
|
|
1019
1348
|
}
|
|
1349
|
+
toDebugProtocolBreakpoint(breakpoint) {
|
|
1350
|
+
const dbp = new Breakpoint(breakpoint.verified, breakpoint.line);
|
|
1351
|
+
dbp.id = breakpoint.id;
|
|
1352
|
+
return dbp;
|
|
1353
|
+
}
|
|
1020
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}`);
|
|
1021
1356
|
if (!this.runtime || !this.loadedModule || this.pendingBreakpointSources.size === 0) {
|
|
1357
|
+
this.log("Breakpoint update skipped: runtime, module, or pending source is unavailable");
|
|
1022
1358
|
onComplete?.();
|
|
1023
1359
|
return;
|
|
1024
1360
|
}
|
|
@@ -1028,33 +1364,40 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
1028
1364
|
}
|
|
1029
1365
|
this.runtime.pause();
|
|
1030
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;
|
|
1378
|
+
}
|
|
1379
|
+
runtime.finishModuleLoad();
|
|
1380
|
+
}
|
|
1031
1381
|
applyPendingBreakpointUpdates(runtime, onComplete) {
|
|
1032
1382
|
const wasmModule = this.loadedModule;
|
|
1033
1383
|
if (!wasmModule) {
|
|
1384
|
+
this.log("Breakpoint update skipped: no loaded wasm module");
|
|
1034
1385
|
onComplete?.();
|
|
1035
1386
|
return;
|
|
1036
1387
|
}
|
|
1037
1388
|
if (this.pendingBreakpointSources.size === 0) {
|
|
1389
|
+
this.log("Breakpoint update skipped: no pending breakpoint sources");
|
|
1038
1390
|
onComplete?.();
|
|
1039
1391
|
return;
|
|
1040
1392
|
}
|
|
1393
|
+
this.log(`Applying breakpoint updates: requestedSources=${this.requestedBreakpointsBySource.size}, pendingSources=${this.pendingBreakpointSources.size}`);
|
|
1041
1394
|
this.pendingBreakpointSources.clear();
|
|
1042
|
-
const wasmBytecodeOffsets =
|
|
1043
|
-
for (const [sourcePath, breakpoints] of this.requestedBreakpointsBySource) {
|
|
1044
|
-
for (const breakpoint of breakpoints) {
|
|
1045
|
-
const breakpointLocations = wasmModule.resolveBreakpointLocations(breakpoint);
|
|
1046
|
-
if (breakpointLocations.length === 0) {
|
|
1047
|
-
this.log(`No source-map match for breakpoint ${path.basename(sourcePath)}:${breakpoint.line}`);
|
|
1048
|
-
}
|
|
1049
|
-
for (const location of breakpointLocations) {
|
|
1050
|
-
wasmBytecodeOffsets.add(location.wasmBytecodeOffset);
|
|
1051
|
-
this.log(`Resolved breakpoint ${path.basename(sourcePath)}:${breakpoint.line} -> ${location.wasmBytecodeOffset}`);
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1395
|
+
const wasmBytecodeOffsets = this.collectBreakpointOffsets(wasmModule);
|
|
1055
1396
|
runtime.setWasmBreakpoints(wasmModule, Array.from(wasmBytecodeOffsets).toSorted((left, right) => left - right), {
|
|
1056
1397
|
onSuccess: () => {
|
|
1057
|
-
this.log(`Breakpoint set installed: ${wasmBytecodeOffsets.size} locations
|
|
1398
|
+
this.log(`Breakpoint set installed: ${wasmBytecodeOffsets.size} locations, offsets=${Array.from(wasmBytecodeOffsets)
|
|
1399
|
+
.toSorted((left, right) => left - right)
|
|
1400
|
+
.join(",") || "none"}`);
|
|
1058
1401
|
onComplete?.();
|
|
1059
1402
|
},
|
|
1060
1403
|
onError: (error) => {
|
|
@@ -1063,6 +1406,32 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
1063
1406
|
},
|
|
1064
1407
|
});
|
|
1065
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
|
+
}
|
|
1434
|
+
}
|
|
1066
1435
|
disposeLoadedModule() {
|
|
1067
1436
|
this.clearVariableContainers();
|
|
1068
1437
|
this.loadedModule?.dispose();
|