warpo 3.1.3 → 3.2.1
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/assemblyscript/std/README.md +5 -0
- package/assemblyscript/std/assembly/array.ts +542 -0
- package/assemblyscript/std/assembly/arraybuffer.ts +72 -0
- package/assemblyscript/std/assembly/builtins.ts +2312 -0
- package/assemblyscript/std/assembly/compat.ts +2 -0
- package/assemblyscript/std/assembly/dataview.ts +144 -0
- package/assemblyscript/std/assembly/date.ts +383 -0
- package/assemblyscript/std/assembly/diagnostics.ts +11 -0
- package/assemblyscript/std/assembly/error.ts +39 -0
- package/assemblyscript/std/assembly/function.ts +44 -0
- package/assemblyscript/std/assembly/iterator.ts +24 -0
- package/assemblyscript/std/assembly/map.ts +304 -0
- package/assemblyscript/std/assembly/math.ts +3401 -0
- package/assemblyscript/std/assembly/memory.ts +110 -0
- package/assemblyscript/std/assembly/number.ts +388 -0
- package/assemblyscript/std/assembly/object.ts +30 -0
- package/assemblyscript/std/assembly/polyfills.ts +24 -0
- package/assemblyscript/std/assembly/process.ts +7 -0
- package/assemblyscript/std/assembly/promise.ts +172 -0
- package/assemblyscript/std/assembly/reference.ts +48 -0
- package/assemblyscript/std/assembly/regexp.ts +16 -0
- package/assemblyscript/std/assembly/rt/common.ts +79 -0
- package/assemblyscript/std/assembly/rt/index-incremental.ts +4 -0
- package/assemblyscript/std/assembly/rt/index-radical.ts +4 -0
- package/assemblyscript/std/assembly/rt/index.d.ts +37 -0
- package/assemblyscript/std/assembly/rt/itcms.ts +415 -0
- package/assemblyscript/std/assembly/rt/tcms.ts +276 -0
- package/assemblyscript/std/assembly/rt/tlsf.ts +564 -0
- package/assemblyscript/std/assembly/rt.ts +71 -0
- package/assemblyscript/std/assembly/set.ts +261 -0
- package/assemblyscript/std/assembly/shared/feature.ts +69 -0
- package/assemblyscript/std/assembly/shared/host.ts +12 -0
- package/assemblyscript/std/assembly/shared/runtime.ts +9 -0
- package/assemblyscript/std/assembly/shared/target.ts +9 -0
- package/assemblyscript/std/assembly/shared/tsconfig.json +9 -0
- package/assemblyscript/std/assembly/shared/typeinfo.ts +72 -0
- package/assemblyscript/std/assembly/staticarray.ts +446 -0
- package/assemblyscript/std/assembly/string.ts +892 -0
- package/assemblyscript/std/assembly/symbol.ts +61 -0
- package/assemblyscript/std/assembly/table.ts +15 -0
- package/assemblyscript/std/assembly/tsconfig.json +4 -0
- package/assemblyscript/std/assembly/tuple.ts +36 -0
- package/assemblyscript/std/assembly/typedarray.ts +2000 -0
- package/assemblyscript/std/assembly/uri.ts +17 -0
- package/assemblyscript/std/assembly/util/bytes.ts +90 -0
- package/assemblyscript/std/assembly/util/casemap.ts +264 -0
- package/assemblyscript/std/assembly/util/error.ts +54 -0
- package/assemblyscript/std/assembly/util/hash.ts +117 -0
- package/assemblyscript/std/assembly/util/math.ts +1922 -0
- package/assemblyscript/std/assembly/util/memory.ts +309 -0
- package/assemblyscript/std/assembly/util/number.ts +884 -0
- package/assemblyscript/std/assembly/util/sort.ts +282 -0
- package/assemblyscript/std/assembly/util/string.ts +1075 -0
- package/assemblyscript/std/assembly/util/uri.ts +251 -0
- package/assemblyscript/std/assembly/vector.ts +3 -0
- 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 -1
- package/dist/debug_server/debugSession.js +477 -99
- 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 +9 -0
- package/dist/debug_server/debuggerWasmModule.js +182 -4
- 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 +556 -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_closure_stepping.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_closure_stepping.js +12 -0
- package/dist/debug_server/tests/testModule/debugger_closure_stepping.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_std_array.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_std_array.js +6 -0
- package/dist/debug_server/tests/testModule/debugger_std_array.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_string_stepping.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_string_stepping.js +9 -0
- package/dist/debug_server/tests/testModule/debugger_string_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 +7 -7
- package/warpo_extension/std/tsconfig.json +4 -0
- package/warpo_extension/std/warpo/ffi.ts +16 -0
- package/warpo_extension/std/warpo/test/comparison.ts +122 -0
- package/warpo_extension/std/warpo/test/env.ts +33 -0
- package/warpo_extension/std/warpo/test/expect.ts +101 -0
- package/warpo_extension/std/warpo/test/formatPrint.ts +99 -0
- package/warpo_extension/std/warpo/test/implement.ts +41 -0
- package/warpo_extension/std/warpo/test/index.d.ts +0 -0
- package/warpo_extension/std/warpo/test/index.ts +39 -0
- package/warpo_extension/std/warpo/test/mockInstrument.ts +26 -0
- package/warpo_extension/std/warpo/utf8/const_str.ts +17 -0
- package/warpo_extension/std/wasi_snapshot_preview1/LICENSE +202 -0
- package/warpo_extension/std/wasi_snapshot_preview1/README.md +1 -0
- package/warpo_extension/std/wasi_snapshot_preview1/bindings/wasi_snapshot_preview1.ts +1731 -0
- package/warpo_extension/std/wasi_snapshot_preview1/wasi_console.ts +96 -0
- package/warpo_extension/std/wasi_snapshot_preview1/wasi_crypto.ts +8 -0
- package/warpo_extension/std/wasi_snapshot_preview1/wasi_date.ts +13 -0
- package/warpo_extension/std/wasi_snapshot_preview1/wasi_internal.ts +119 -0
- package/warpo_extension/std/wasi_snapshot_preview1/wasi_performance.ts +11 -0
- package/warpo_extension/std/wasi_snapshot_preview1/wasi_process.ts +191 -0
|
@@ -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();
|
|
@@ -254,13 +357,10 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
254
357
|
const id = index + 1;
|
|
255
358
|
this.pausedStackFramesById.set(id, { ...frame, frameIndex: index });
|
|
256
359
|
const sourceLocation = this.resolveSourceLocation(frame.wasmBytecodeOffset);
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
: "unknown";
|
|
360
|
+
const sourcePath = sourceLocation ? this.loadedModule?.patchSourcePath(sourceLocation.sourcePath) : undefined;
|
|
361
|
+
const sourceLabel = sourceLocation && sourcePath ? `${path.basename(sourcePath)}:${sourceLocation.sourceLine}` : "unknown";
|
|
260
362
|
this.log(` frame ${id}: index=${index}, name=${frame.functionName || "wasm"}, offset=${frame.wasmBytecodeOffset ?? "unknown"}, source=${sourceLabel}`);
|
|
261
|
-
const source =
|
|
262
|
-
? new Source(path.basename(sourceLocation.sourcePath), sourceLocation.sourcePath)
|
|
263
|
-
: undefined;
|
|
363
|
+
const source = sourcePath ? new Source(path.basename(sourcePath), sourcePath) : undefined;
|
|
264
364
|
return new StackFrame(id, frame.functionName || "wasm", source, sourceLocation?.sourceLine ?? 0, 1);
|
|
265
365
|
});
|
|
266
366
|
const startFrame = args.startFrame ?? 0;
|
|
@@ -301,6 +401,16 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
301
401
|
response.body = { variables: await this.decodeObjectAtAddress(variableContainer.address) };
|
|
302
402
|
break;
|
|
303
403
|
}
|
|
404
|
+
case "variables": {
|
|
405
|
+
response.body = { variables: variableContainer.variables };
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
case "closure-scopes": {
|
|
409
|
+
response.body = {
|
|
410
|
+
variables: await this.resolveClosureScopeVariables(variableContainer.functionName, variableContainer.envAddress, variableContainer.scopeLevels),
|
|
411
|
+
};
|
|
412
|
+
break;
|
|
413
|
+
}
|
|
304
414
|
case "map-entry": {
|
|
305
415
|
response.body = {
|
|
306
416
|
variables: [variableContainer.key, variableContainer.value].map((variable) => this.toDebugProtocolVariable(variable)),
|
|
@@ -366,24 +476,78 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
366
476
|
}
|
|
367
477
|
const sourceVariables = sourceVariableScope.variables;
|
|
368
478
|
const rawWasmValues = await this.resolveRawWasmValues(sourceVariables, runtimeVariablesByIndex, runtime);
|
|
369
|
-
const
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
479
|
+
const firstTupleAddress = this.resolveClosureTupleAddress(sourceVariableScope.rootClosureEnvLocalIndex, runtimeVariablesByIndex);
|
|
480
|
+
const decodedScopes = await this.decodeScopeChainVariables([sourceVariableScope], firstTupleAddress, rawWasmValues);
|
|
481
|
+
return decodedScopes[0] ?? [];
|
|
482
|
+
}
|
|
483
|
+
async decodeScopeChainVariables(scopeLevels, firstTupleAddress, rawWasmValues) {
|
|
484
|
+
const closureTupleValuesByLevel = new Map();
|
|
485
|
+
const visitedAddresses = new Set();
|
|
486
|
+
if (firstTupleAddress !== undefined && Number.isInteger(firstTupleAddress) && firstTupleAddress > 0) {
|
|
487
|
+
let currentAddress = firstTupleAddress;
|
|
488
|
+
let tupleLevel = 0;
|
|
489
|
+
while (!visitedAddresses.has(currentAddress)) {
|
|
490
|
+
visitedAddresses.add(currentAddress);
|
|
491
|
+
const tupleValues = await this.readTupleElementValues(currentAddress);
|
|
492
|
+
const levelValues = new Map();
|
|
493
|
+
for (const [index, value] of tupleValues.entries()) {
|
|
494
|
+
levelValues.set(index, value.value);
|
|
495
|
+
}
|
|
496
|
+
closureTupleValuesByLevel.set(tupleLevel, levelValues);
|
|
497
|
+
const parentTuple = tupleValues[0];
|
|
498
|
+
if (!parentTuple) {
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
const parentAddress = Number(parentTuple.value);
|
|
502
|
+
if (!Number.isInteger(parentAddress) || parentAddress <= 0) {
|
|
503
|
+
break;
|
|
378
504
|
}
|
|
379
|
-
|
|
505
|
+
currentAddress = parentAddress;
|
|
506
|
+
tupleLevel++;
|
|
380
507
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
508
|
+
}
|
|
509
|
+
return Promise.all(scopeLevels.map(async (scopeLevel) => {
|
|
510
|
+
const rawLocalValues = scopeLevel.variables.map((variable) => {
|
|
511
|
+
if (variable.kind === "closure") {
|
|
512
|
+
const elementIndex = variable.fieldOffset / SMALL_TUPLE_SLOT_SIZE;
|
|
513
|
+
const levelValues = closureTupleValuesByLevel.get(scopeLevel.tupleLevel ?? 0);
|
|
514
|
+
const tupleRawValue = levelValues?.get(elementIndex);
|
|
515
|
+
if (tupleRawValue === undefined) {
|
|
516
|
+
return { name: variable.name, value: "<unavailable>", typeName: variable.typeName };
|
|
517
|
+
}
|
|
518
|
+
return { name: variable.name, value: tupleRawValue, typeName: variable.typeName };
|
|
519
|
+
}
|
|
520
|
+
const wasmRawValue = rawWasmValues.get(variable);
|
|
521
|
+
return {
|
|
522
|
+
name: variable.name,
|
|
523
|
+
value: wasmRawValue ?? "<unavailable>",
|
|
524
|
+
typeName: variable.typeName,
|
|
525
|
+
};
|
|
526
|
+
});
|
|
527
|
+
const variables = await Promise.all(rawLocalValues.map((variable) => this.toDebugSessionVariableValue(variable.name, variable.typeName, variable.value)));
|
|
528
|
+
return variables.map((variable) => this.toDebugProtocolVariable(variable));
|
|
529
|
+
}));
|
|
530
|
+
}
|
|
531
|
+
async resolveClosureScopeVariables(functionName, envAddress, scopeLevels) {
|
|
532
|
+
const wasmModule = this.loadedModule;
|
|
533
|
+
if (!wasmModule) {
|
|
534
|
+
this.log(`Warning: cannot expand closure scopes for ${functionName}: wasm module is not loaded`);
|
|
535
|
+
return [];
|
|
536
|
+
}
|
|
537
|
+
if (!scopeLevels) {
|
|
538
|
+
this.log(`Warning: cannot expand closure scopes for ${functionName}: DWARF function is unavailable`);
|
|
539
|
+
return [];
|
|
540
|
+
}
|
|
541
|
+
const decodedScopeVariables = await this.decodeScopeChainVariables(scopeLevels, envAddress, new Map());
|
|
542
|
+
return scopeLevels.map((scopeLevel, index) => {
|
|
543
|
+
const variable = {
|
|
544
|
+
kind: "synthetic",
|
|
545
|
+
name: index.toString(),
|
|
546
|
+
value: scopeLevel.name,
|
|
547
|
+
container: { kind: "variables", variables: decodedScopeVariables[index] ?? [] },
|
|
548
|
+
};
|
|
549
|
+
return this.toDebugProtocolVariable(variable);
|
|
384
550
|
});
|
|
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
551
|
}
|
|
388
552
|
async decodeObjectAtAddress(address) {
|
|
389
553
|
const fields = await this.resolveObjectFields(address);
|
|
@@ -408,39 +572,16 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
408
572
|
}
|
|
409
573
|
return rawWasmValues;
|
|
410
574
|
}
|
|
411
|
-
|
|
412
|
-
const closureTupleValuesByLevel = new Map();
|
|
575
|
+
resolveClosureTupleAddress(closureEnvLocalIndex, runtimeVariablesByIndex) {
|
|
413
576
|
if (closureEnvLocalIndex === undefined) {
|
|
414
|
-
return
|
|
577
|
+
return undefined;
|
|
415
578
|
}
|
|
416
579
|
const closureEnvVariable = runtimeVariablesByIndex.get(closureEnvLocalIndex);
|
|
417
580
|
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++;
|
|
581
|
+
return undefined;
|
|
442
582
|
}
|
|
443
|
-
|
|
583
|
+
const address = Number(closureEnvVariable.value);
|
|
584
|
+
return Number.isInteger(address) && address > 0 ? address : undefined;
|
|
444
585
|
}
|
|
445
586
|
resolvePausedStackFrame(frameId) {
|
|
446
587
|
return this.pausedStackFramesById.get(frameId);
|
|
@@ -452,7 +593,7 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
452
593
|
if (typeName !== undefined) {
|
|
453
594
|
const objectAddress = this.parseObjectAddress(typeName, value);
|
|
454
595
|
if (objectAddress) {
|
|
455
|
-
if (typeName
|
|
596
|
+
if (isDisplayableStringClass(typeName)) {
|
|
456
597
|
const stringValue = await this.decodeStringAtAddress(typeName, objectAddress);
|
|
457
598
|
return this.createClassVariable(name, typeName, objectAddress, stringValue);
|
|
458
599
|
}
|
|
@@ -487,12 +628,18 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
487
628
|
case "map-entry": {
|
|
488
629
|
return "";
|
|
489
630
|
}
|
|
631
|
+
case "synthetic": {
|
|
632
|
+
return variable.value;
|
|
633
|
+
}
|
|
490
634
|
}
|
|
491
635
|
}
|
|
492
636
|
getVariableReference(variable, isExpandableClass) {
|
|
493
637
|
if (isExpandableClass && variable.kind === "class") {
|
|
494
638
|
return this.toObjectVariablesReference(variable.address);
|
|
495
639
|
}
|
|
640
|
+
if (variable.kind === "synthetic") {
|
|
641
|
+
return variable.container === undefined ? 0 : this.createVariableContainer(variable.container);
|
|
642
|
+
}
|
|
496
643
|
if (variable.kind === "map-entry") {
|
|
497
644
|
return this.createVariableContainer({ kind: "map-entry", key: variable.key, value: variable.value });
|
|
498
645
|
}
|
|
@@ -502,7 +649,7 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
502
649
|
return { kind: "class", name, typeName, address, displayValue };
|
|
503
650
|
}
|
|
504
651
|
parseObjectAddress(typeName, value) {
|
|
505
|
-
if (!this.loadedModule?.getClassLayout(typeName)) {
|
|
652
|
+
if (typeName !== UTF8_CONST_STR_CLASS_NAME && !this.loadedModule?.getClassLayout(typeName)) {
|
|
506
653
|
return undefined;
|
|
507
654
|
}
|
|
508
655
|
const address = Number(value);
|
|
@@ -599,9 +746,16 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
599
746
|
return [];
|
|
600
747
|
}
|
|
601
748
|
const view = new DataView(memory.buffer, memory.byteOffset, memory.byteLength);
|
|
749
|
+
if (classLayout.builtinKind === BuiltinContainerKind.Function) {
|
|
750
|
+
return this.decodeFunctionElements(view, classLayout);
|
|
751
|
+
}
|
|
602
752
|
if (classLayout.builtinKind === BuiltinContainerKind.ArrayBuffer) {
|
|
603
753
|
return this.decodeArrayBufferElements(view);
|
|
604
754
|
}
|
|
755
|
+
const typedArrayElementType = TYPED_ARRAY_ELEMENT_TYPES.get(classLayout.name);
|
|
756
|
+
if (typedArrayElementType !== undefined) {
|
|
757
|
+
return this.decodeTypedArrayElements(address, view, classLayout, typedArrayElementType);
|
|
758
|
+
}
|
|
605
759
|
if (classLayout.builtinKind === BuiltinContainerKind.Array) {
|
|
606
760
|
return this.decodeArrayElements(address, view, classLayout);
|
|
607
761
|
}
|
|
@@ -616,10 +770,49 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
616
770
|
}
|
|
617
771
|
return Promise.all(classLayout.fields.map((field) => this.decodeFieldVariable(view, field)));
|
|
618
772
|
}
|
|
773
|
+
decodeFunctionElements(view, classLayout) {
|
|
774
|
+
const indexField = classLayout.fields.find((field) => field.name === "_index" || field.name === "index");
|
|
775
|
+
let functionName;
|
|
776
|
+
if (indexField) {
|
|
777
|
+
const indexValue = this.decodeFieldValue(view, indexField);
|
|
778
|
+
const functionIndex = Number(indexValue);
|
|
779
|
+
if (Number.isInteger(functionIndex) && functionIndex >= 0) {
|
|
780
|
+
functionName = this.loadedModule?.getFunctionNameByTableIndex(functionIndex);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
else {
|
|
784
|
+
this.log(`Warning: cannot expand function: index field is unavailable`);
|
|
785
|
+
}
|
|
786
|
+
const envField = classLayout.fields.find((field) => field.name === "_env" || field.name === "env");
|
|
787
|
+
let envAddress;
|
|
788
|
+
if (envField) {
|
|
789
|
+
const envValue = Number(this.decodeFieldValue(view, envField));
|
|
790
|
+
if (Number.isInteger(envValue) && envValue > 0) {
|
|
791
|
+
envAddress = envValue;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
else {
|
|
795
|
+
this.log(`Warning: cannot expand function: env field is unavailable`);
|
|
796
|
+
}
|
|
797
|
+
const closureScopes = functionName === undefined ? undefined : this.loadedModule?.getClosureVariableScopesByFunctionName(functionName);
|
|
798
|
+
return [
|
|
799
|
+
{ kind: "basic", name: "name", value: functionName ?? "<unavailable>", typeName: "string" },
|
|
800
|
+
{
|
|
801
|
+
kind: "synthetic",
|
|
802
|
+
name: "[[scopes]]",
|
|
803
|
+
value: `Scopes[${closureScopes?.length ?? 0}]`,
|
|
804
|
+
typeName: "Scopes",
|
|
805
|
+
container: functionName === undefined || envAddress === undefined
|
|
806
|
+
? undefined
|
|
807
|
+
: { kind: "closure-scopes", functionName, envAddress, scopeLevels: closureScopes },
|
|
808
|
+
},
|
|
809
|
+
];
|
|
810
|
+
}
|
|
619
811
|
async resolveRuntimeObjectPayloadSize(address, classLayout) {
|
|
620
812
|
if (classLayout.builtinKind !== BuiltinContainerKind.SmallTuple &&
|
|
621
813
|
classLayout.builtinKind !== BuiltinContainerKind.StaticArray &&
|
|
622
|
-
classLayout.builtinKind !== BuiltinContainerKind.ArrayBuffer
|
|
814
|
+
classLayout.builtinKind !== BuiltinContainerKind.ArrayBuffer &&
|
|
815
|
+
!TYPED_ARRAY_ELEMENT_TYPES.has(classLayout.name)) {
|
|
623
816
|
return classLayout.byteSize;
|
|
624
817
|
}
|
|
625
818
|
const sizeMemory = await this.runtime?.readWasmMemory(address + OBJECT_RTSIZE_OFFSET, OBJECT_RTSIZE_SIZE);
|
|
@@ -659,7 +852,7 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
659
852
|
this.log(`Error: cannot expand tuple reference at ${elementAddress}: runtime class layout is missing`);
|
|
660
853
|
return { kind: "basic", name, value: elementAddress.toString(), typeName: undefined };
|
|
661
854
|
}
|
|
662
|
-
if (classLayout.name
|
|
855
|
+
if (isDisplayableStringClass(classLayout.name)) {
|
|
663
856
|
const stringValue = await this.decodeStringAtAddress(classLayout.name, elementAddress);
|
|
664
857
|
return this.createClassVariable(name, classLayout.name, elementAddress, stringValue);
|
|
665
858
|
}
|
|
@@ -699,6 +892,32 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
699
892
|
}
|
|
700
893
|
return elements;
|
|
701
894
|
}
|
|
895
|
+
async decodeTypedArrayElements(address, view, classLayout, elementTypeName) {
|
|
896
|
+
const dataStartField = classLayout.fields.find((field) => field.name === "dataStart");
|
|
897
|
+
const byteLengthField = classLayout.fields.find((field) => field.name === "byteLength");
|
|
898
|
+
if (!dataStartField || !byteLengthField) {
|
|
899
|
+
this.log(`Error: cannot expand typed array at ${address}: typed array header is unavailable`);
|
|
900
|
+
return [];
|
|
901
|
+
}
|
|
902
|
+
const dataStart = Number(this.decodeFieldValue(view, dataStartField));
|
|
903
|
+
const byteLength = Number(this.decodeFieldValue(view, byteLengthField));
|
|
904
|
+
const elementSize = resolveArrayElementSize(elementTypeName, false);
|
|
905
|
+
if (!Number.isInteger(dataStart) ||
|
|
906
|
+
dataStart < 0 ||
|
|
907
|
+
!Number.isInteger(byteLength) ||
|
|
908
|
+
byteLength < 0 ||
|
|
909
|
+
byteLength % elementSize !== 0) {
|
|
910
|
+
this.log(`Error: cannot expand typed array at ${address}: typed array data is invalid`);
|
|
911
|
+
return [];
|
|
912
|
+
}
|
|
913
|
+
const elementsMemory = await this.runtime?.readWasmMemory(dataStart, byteLength);
|
|
914
|
+
if (!elementsMemory) {
|
|
915
|
+
this.log(`Error: cannot expand typed array at ${address}: typed array data is unavailable`);
|
|
916
|
+
return [];
|
|
917
|
+
}
|
|
918
|
+
const elementsView = new DataView(elementsMemory.buffer, elementsMemory.byteOffset, elementsMemory.byteLength);
|
|
919
|
+
return this.decodeArrayElementVariables(elementsView, byteLength / elementSize, elementTypeName, elementSize, false);
|
|
920
|
+
}
|
|
702
921
|
async decodeStaticArrayElements(address, view, classLayout) {
|
|
703
922
|
assert.equal(classLayout.builtinKind, BuiltinContainerKind.StaticArray);
|
|
704
923
|
const elementTypeName = classLayout.templateType;
|
|
@@ -857,7 +1076,7 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
857
1076
|
const value = this.decodeFieldValue(view, field);
|
|
858
1077
|
const address = field.isReference ? this.parseObjectAddress(field.typeName, value) : undefined;
|
|
859
1078
|
if (address) {
|
|
860
|
-
if (field.typeName
|
|
1079
|
+
if (isDisplayableStringClass(field.typeName)) {
|
|
861
1080
|
const stringValue = await this.decodeStringAtAddress(field.typeName, address);
|
|
862
1081
|
return this.createClassVariable(field.name, field.typeName, address, stringValue);
|
|
863
1082
|
}
|
|
@@ -939,7 +1158,7 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
939
1158
|
}
|
|
940
1159
|
}
|
|
941
1160
|
async decodeStringAtAddress(typeName, address) {
|
|
942
|
-
assert.
|
|
1161
|
+
assert.ok(isDisplayableStringClass(typeName));
|
|
943
1162
|
if (!this.runtime) {
|
|
944
1163
|
this.log(`Error: cannot decode string at ${address}: runtime is not active`);
|
|
945
1164
|
return "<unavailable>";
|
|
@@ -956,7 +1175,8 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
956
1175
|
this.log(`Error: cannot decode string at ${address}: string payload memory is unavailable`);
|
|
957
1176
|
return "<unavailable>";
|
|
958
1177
|
}
|
|
959
|
-
|
|
1178
|
+
const encoding = typeName === UTF8_CONST_STR_CLASS_NAME ? "utf8" : "utf16le";
|
|
1179
|
+
return JSON.stringify(Buffer.from(stringMemory).toString(encoding));
|
|
960
1180
|
}
|
|
961
1181
|
doContinueRequest(response) {
|
|
962
1182
|
const runtime = this.runtime;
|
|
@@ -965,13 +1185,119 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
965
1185
|
return;
|
|
966
1186
|
}
|
|
967
1187
|
response.body = { allThreadsContinued: true };
|
|
968
|
-
this.sendResponse(response);
|
|
969
1188
|
if (this.stoppedForTrap) {
|
|
1189
|
+
this.sendResponse(response);
|
|
970
1190
|
this.log("Continue requested from wasm trap; terminating debug session");
|
|
971
|
-
this.disposeRuntimeAndTerminate(runtime);
|
|
1191
|
+
void this.disposeRuntimeAndTerminate(runtime);
|
|
972
1192
|
return;
|
|
973
1193
|
}
|
|
1194
|
+
this.cancelStep();
|
|
974
1195
|
void this.resumeRuntime(runtime);
|
|
1196
|
+
this.sendResponse(response);
|
|
1197
|
+
}
|
|
1198
|
+
doStepRequest(response, mode) {
|
|
1199
|
+
const runtime = this.runtime;
|
|
1200
|
+
if (!runtime) {
|
|
1201
|
+
this.sendErrorResponse(response, 1, "No active runtime");
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
if (!runtime.isPaused()) {
|
|
1205
|
+
this.sendErrorResponse(response, 1, "Runtime is not paused");
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
if (this.stoppedForTrap) {
|
|
1209
|
+
this.sendResponse(response);
|
|
1210
|
+
this.log("Step requested from wasm trap; terminating debug session");
|
|
1211
|
+
void this.disposeRuntimeAndTerminate(runtime);
|
|
1212
|
+
return;
|
|
1213
|
+
}
|
|
1214
|
+
this.stepMode = mode;
|
|
1215
|
+
this.stepStartSourceLocation = this.resolveSourceLocation(this.stoppedWasmBytecodeOffset);
|
|
1216
|
+
this.clearVariableContainers();
|
|
1217
|
+
this.sendResponse(response);
|
|
1218
|
+
this.sendEvent(new ContinuedEvent(WarpoDebugSession.threadId, true));
|
|
1219
|
+
void this.advanceStep(runtime).catch((error) => {
|
|
1220
|
+
this.handleStepFailure(error);
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
async handleStepPause(runtime, info) {
|
|
1224
|
+
try {
|
|
1225
|
+
const sourceLocation = this.resolveSourceLocation(info.wasmBytecodeOffset);
|
|
1226
|
+
if (this.stepStartSourceLocation &&
|
|
1227
|
+
!this.isLibrarySource(this.stepStartSourceLocation.sourcePath) &&
|
|
1228
|
+
sourceLocation &&
|
|
1229
|
+
this.isLibrarySource(sourceLocation.sourcePath)) {
|
|
1230
|
+
this.log("Skipping library source location during step");
|
|
1231
|
+
await runtime.stepOut();
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
if (this.hasReachedNextSourceLine(info.wasmBytecodeOffset)) {
|
|
1235
|
+
this.completeStep(runtime, info);
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
await this.advanceStep(runtime);
|
|
1239
|
+
}
|
|
1240
|
+
catch (error) {
|
|
1241
|
+
this.handleStepFailure(error);
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
handleNonStepPause(runtime, info) {
|
|
1245
|
+
this.cancelStep();
|
|
1246
|
+
if (this.runtime !== runtime) {
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1249
|
+
this.sendStoppedEvent(runtime, info);
|
|
1250
|
+
}
|
|
1251
|
+
hasReachedNextSourceLine(wasmBytecodeOffset) {
|
|
1252
|
+
const sourceLocation = this.resolveSourceLocation(wasmBytecodeOffset);
|
|
1253
|
+
if (!sourceLocation) {
|
|
1254
|
+
return false;
|
|
1255
|
+
}
|
|
1256
|
+
const startSourceLocation = this.stepStartSourceLocation;
|
|
1257
|
+
return (startSourceLocation === undefined ||
|
|
1258
|
+
sourceLocation.sourcePath !== startSourceLocation.sourcePath ||
|
|
1259
|
+
sourceLocation.sourceLine !== startSourceLocation.sourceLine);
|
|
1260
|
+
}
|
|
1261
|
+
isLibrarySource(sourcePath) {
|
|
1262
|
+
return sourcePath.startsWith("~lib/");
|
|
1263
|
+
}
|
|
1264
|
+
async advanceStep(runtime) {
|
|
1265
|
+
if (this.runtime !== runtime || this.stepMode === StepMode.None) {
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
const wasmModule = this.loadedModule;
|
|
1269
|
+
const wasmBytecodeOffset = this.stoppedWasmBytecodeOffset;
|
|
1270
|
+
if (this.stepMode === StepMode.Out) {
|
|
1271
|
+
this.stepMode = StepMode.Into;
|
|
1272
|
+
await runtime.stepOut();
|
|
1273
|
+
}
|
|
1274
|
+
else if (this.stepMode === StepMode.Over &&
|
|
1275
|
+
wasmModule &&
|
|
1276
|
+
wasmBytecodeOffset !== undefined &&
|
|
1277
|
+
wasmModule.getNextBytecodeOffsetAfterCall(wasmBytecodeOffset) !== undefined) {
|
|
1278
|
+
await runtime.stepOver();
|
|
1279
|
+
}
|
|
1280
|
+
else {
|
|
1281
|
+
// Source-level stepping advances through runtime primitives until the source location changes. Calls use the
|
|
1282
|
+
// runtime's call-aware operation because their following bytecode may be a structural `end`, not an executable instruction.
|
|
1283
|
+
await runtime.stepInstruction();
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
completeStep(runtime, info) {
|
|
1287
|
+
this.cancelStep();
|
|
1288
|
+
if (this.runtime !== runtime) {
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1291
|
+
this.clearVariableContainers();
|
|
1292
|
+
this.sendStoppedEvent(runtime, info);
|
|
1293
|
+
}
|
|
1294
|
+
handleStepFailure(error) {
|
|
1295
|
+
this.log(`Step failed: ${formatUnknownError(error)}`);
|
|
1296
|
+
this.cancelStep();
|
|
1297
|
+
}
|
|
1298
|
+
cancelStep() {
|
|
1299
|
+
this.stepMode = StepMode.None;
|
|
1300
|
+
this.stepStartSourceLocation = undefined;
|
|
975
1301
|
}
|
|
976
1302
|
async resumeRuntime(runtime) {
|
|
977
1303
|
try {
|
|
@@ -992,13 +1318,18 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
992
1318
|
if (this.runtime !== runtime) {
|
|
993
1319
|
return;
|
|
994
1320
|
}
|
|
995
|
-
this.disposeRuntimeAndTerminate(runtime);
|
|
1321
|
+
void this.disposeRuntimeAndTerminate(runtime);
|
|
996
1322
|
}
|
|
997
|
-
disposeRuntimeAndTerminate(runtime) {
|
|
998
|
-
runtime.dispose();
|
|
1323
|
+
async disposeRuntimeAndTerminate(runtime) {
|
|
999
1324
|
if (this.runtime === runtime) {
|
|
1000
1325
|
this.runtime = undefined;
|
|
1001
1326
|
}
|
|
1327
|
+
try {
|
|
1328
|
+
await runtime.dispose();
|
|
1329
|
+
}
|
|
1330
|
+
catch (error) {
|
|
1331
|
+
this.log(`Failed to dispose runtime: ${formatUnknownError(error)}`);
|
|
1332
|
+
}
|
|
1002
1333
|
this.resetRuntimeState();
|
|
1003
1334
|
this.sendEvent(new TerminatedEvent());
|
|
1004
1335
|
}
|
|
@@ -1007,6 +1338,13 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
1007
1338
|
this.pendingBreakpointSources.clear();
|
|
1008
1339
|
this.stoppedWasmBytecodeOffset = undefined;
|
|
1009
1340
|
this.stoppedForTrap = false;
|
|
1341
|
+
this.stepMode = StepMode.None;
|
|
1342
|
+
this.stepStartSourceLocation = undefined;
|
|
1343
|
+
}
|
|
1344
|
+
queueAllBreakpointUpdates() {
|
|
1345
|
+
for (const sourcePath of this.requestedBreakpointsBySource.keys()) {
|
|
1346
|
+
this.pendingBreakpointSources.add(sourcePath);
|
|
1347
|
+
}
|
|
1010
1348
|
}
|
|
1011
1349
|
logAllBreakpoints() {
|
|
1012
1350
|
this.log("=== Registered Breakpoints ===");
|
|
@@ -1017,8 +1355,15 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
1017
1355
|
}
|
|
1018
1356
|
this.log("==============================");
|
|
1019
1357
|
}
|
|
1358
|
+
toDebugProtocolBreakpoint(breakpoint) {
|
|
1359
|
+
const dbp = new Breakpoint(breakpoint.verified, breakpoint.line);
|
|
1360
|
+
dbp.id = breakpoint.id;
|
|
1361
|
+
return dbp;
|
|
1362
|
+
}
|
|
1020
1363
|
requestBreakpointUpdate(onComplete) {
|
|
1364
|
+
this.log(`Breakpoint update requested: hasRuntime=${this.runtime !== undefined}, hasModule=${this.loadedModule !== undefined}, pendingSources=${this.pendingBreakpointSources.size}, runtimePaused=${this.runtime?.isPaused() ?? false}`);
|
|
1021
1365
|
if (!this.runtime || !this.loadedModule || this.pendingBreakpointSources.size === 0) {
|
|
1366
|
+
this.log("Breakpoint update skipped: runtime, module, or pending source is unavailable");
|
|
1022
1367
|
onComplete?.();
|
|
1023
1368
|
return;
|
|
1024
1369
|
}
|
|
@@ -1028,33 +1373,40 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
1028
1373
|
}
|
|
1029
1374
|
this.runtime.pause();
|
|
1030
1375
|
}
|
|
1376
|
+
maybeStartRuntime() {
|
|
1377
|
+
const runtime = this.runtime;
|
|
1378
|
+
if (!this.configurationDone || !runtime || !this.loadedModule) {
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
if (runtime.isPaused()) {
|
|
1382
|
+
void runtime.resume().catch((error) => {
|
|
1383
|
+
const message = error instanceof Error ? error.message : "unknown error";
|
|
1384
|
+
this.log(`Failed to resume after configuration: ${message}`);
|
|
1385
|
+
});
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
runtime.finishModuleLoad();
|
|
1389
|
+
}
|
|
1031
1390
|
applyPendingBreakpointUpdates(runtime, onComplete) {
|
|
1032
1391
|
const wasmModule = this.loadedModule;
|
|
1033
1392
|
if (!wasmModule) {
|
|
1393
|
+
this.log("Breakpoint update skipped: no loaded wasm module");
|
|
1034
1394
|
onComplete?.();
|
|
1035
1395
|
return;
|
|
1036
1396
|
}
|
|
1037
1397
|
if (this.pendingBreakpointSources.size === 0) {
|
|
1398
|
+
this.log("Breakpoint update skipped: no pending breakpoint sources");
|
|
1038
1399
|
onComplete?.();
|
|
1039
1400
|
return;
|
|
1040
1401
|
}
|
|
1402
|
+
this.log(`Applying breakpoint updates: requestedSources=${this.requestedBreakpointsBySource.size}, pendingSources=${this.pendingBreakpointSources.size}`);
|
|
1041
1403
|
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
|
-
}
|
|
1404
|
+
const wasmBytecodeOffsets = this.collectBreakpointOffsets(wasmModule);
|
|
1055
1405
|
runtime.setWasmBreakpoints(wasmModule, Array.from(wasmBytecodeOffsets).toSorted((left, right) => left - right), {
|
|
1056
1406
|
onSuccess: () => {
|
|
1057
|
-
this.log(`Breakpoint set installed: ${wasmBytecodeOffsets.size} locations
|
|
1407
|
+
this.log(`Breakpoint set installed: ${wasmBytecodeOffsets.size} locations, offsets=${Array.from(wasmBytecodeOffsets)
|
|
1408
|
+
.toSorted((left, right) => left - right)
|
|
1409
|
+
.join(",") || "none"}`);
|
|
1058
1410
|
onComplete?.();
|
|
1059
1411
|
},
|
|
1060
1412
|
onError: (error) => {
|
|
@@ -1063,6 +1415,32 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
1063
1415
|
},
|
|
1064
1416
|
});
|
|
1065
1417
|
}
|
|
1418
|
+
collectBreakpointOffsets(wasmModule) {
|
|
1419
|
+
const wasmBytecodeOffsets = new Set();
|
|
1420
|
+
for (const [sourcePath, breakpoints] of this.requestedBreakpointsBySource) {
|
|
1421
|
+
for (const breakpoint of breakpoints) {
|
|
1422
|
+
this.collectBreakpointLocations(wasmModule, sourcePath, breakpoint, wasmBytecodeOffsets);
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
return wasmBytecodeOffsets;
|
|
1426
|
+
}
|
|
1427
|
+
collectBreakpointLocations(wasmModule, sourcePath, breakpoint, wasmBytecodeOffsets) {
|
|
1428
|
+
const sourceMapHasSource = wasmModule.hasSource(sourcePath);
|
|
1429
|
+
this.log(`Resolving breakpoint: source=${sourcePath}, line=${breakpoint.line}, sourceMapHasSource=${sourceMapHasSource}`);
|
|
1430
|
+
const breakpointLocations = wasmModule.resolveBreakpointLocations(breakpoint);
|
|
1431
|
+
if (breakpointLocations.length > 0 && !breakpoint.verified) {
|
|
1432
|
+
breakpoint.verified = true;
|
|
1433
|
+
this.sendEvent(new BreakpointEvent("changed", this.toDebugProtocolBreakpoint(breakpoint)));
|
|
1434
|
+
}
|
|
1435
|
+
if (breakpointLocations.length === 0) {
|
|
1436
|
+
this.log(`Source-map lookup failed for breakpoint ${path.basename(sourcePath)}:${breakpoint.line}: ${sourceMapHasSource ? "source present but line has no mapping" : "source is absent"}`);
|
|
1437
|
+
}
|
|
1438
|
+
this.log(`Breakpoint resolution result: source=${sourcePath}, line=${breakpoint.line}, locations=${breakpointLocations.length}, offsets=${breakpointLocations.map((location) => location.wasmBytecodeOffset).join(",") || "none"}`);
|
|
1439
|
+
for (const location of breakpointLocations) {
|
|
1440
|
+
wasmBytecodeOffsets.add(location.wasmBytecodeOffset);
|
|
1441
|
+
this.log(`Resolved breakpoint ${path.basename(sourcePath)}:${breakpoint.line} -> ${location.wasmBytecodeOffset}`);
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1066
1444
|
disposeLoadedModule() {
|
|
1067
1445
|
this.clearVariableContainers();
|
|
1068
1446
|
this.loadedModule?.dispose();
|