warpo 3.1.1 → 3.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/asconfig.schema.json +10 -2
- package/dist/debug_server/debugPath.d.ts +1 -0
- package/dist/debug_server/debugPath.js +10 -0
- package/dist/debug_server/debugPath.js.map +1 -0
- package/dist/debug_server/debugSession.d.ts +61 -3
- package/dist/debug_server/debugSession.js +919 -80
- package/dist/debug_server/debugSession.js.map +1 -1
- package/dist/debug_server/debugger.d.ts +30 -2
- package/dist/debug_server/debuggerWasmModule.d.ts +59 -3
- package/dist/debug_server/debuggerWasmModule.js +234 -14
- package/dist/debug_server/debuggerWasmModule.js.map +1 -1
- package/dist/debug_server/nodeDebugger.d.ts +42 -3
- package/dist/debug_server/nodeDebugger.js +648 -59
- package/dist/debug_server/nodeDebugger.js.map +1 -1
- package/dist/debug_server/tests/debugSession.test.js +1154 -26
- package/dist/debug_server/tests/debugSession.test.js.map +1 -1
- package/dist/debug_server/tests/launcher.js +1 -0
- package/dist/debug_server/tests/launcher.js.map +1 -1
- package/dist/debug_server/tests/testModule/debugger_array.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_array.js +9 -0
- package/dist/debug_server/tests/testModule/debugger_array.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_array_buffer.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_array_buffer.js +12 -0
- package/dist/debug_server/tests/testModule/debugger_array_buffer.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_basic.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_basic.js +9 -1
- package/dist/debug_server/tests/testModule/debugger_basic.js.map +1 -1
- package/dist/debug_server/tests/testModule/debugger_breakpoint_update.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_breakpoint_update.js +10 -0
- package/dist/debug_server/tests/testModule/debugger_breakpoint_update.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_callee.d.ts +10 -0
- package/dist/debug_server/tests/testModule/debugger_callee.js +106 -0
- package/dist/debug_server/tests/testModule/debugger_callee.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_caller.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_caller.js +7 -0
- package/dist/debug_server/tests/testModule/debugger_caller.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_class.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_class.js +39 -0
- package/dist/debug_server/tests/testModule/debugger_class.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_class_array.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_class_array.js +15 -0
- package/dist/debug_server/tests/testModule/debugger_class_array.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_closure.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_closure.js +48 -0
- package/dist/debug_server/tests/testModule/debugger_closure.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_closure_if.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_closure_if.js +16 -0
- package/dist/debug_server/tests/testModule/debugger_closure_if.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_generic_function.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_generic_function.js +10 -0
- package/dist/debug_server/tests/testModule/debugger_generic_function.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_global.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_global.js +11 -0
- package/dist/debug_server/tests/testModule/debugger_global.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_map.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_map.js +22 -0
- package/dist/debug_server/tests/testModule/debugger_map.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_member_function.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_member_function.js +16 -0
- package/dist/debug_server/tests/testModule/debugger_member_function.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_scopes.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_scopes.js +22 -0
- package/dist/debug_server/tests/testModule/debugger_scopes.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_set.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_set.js +23 -0
- package/dist/debug_server/tests/testModule/debugger_set.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_static_array.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_static_array.js +21 -0
- package/dist/debug_server/tests/testModule/debugger_static_array.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_string.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_string.js +16 -0
- package/dist/debug_server/tests/testModule/debugger_string.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_trap.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_trap.js +9 -0
- package/dist/debug_server/tests/testModule/debugger_trap.js.map +1 -0
- package/dist/debug_server/tests/testModule/debugger_tuple.d.ts +1 -0
- package/dist/debug_server/tests/testModule/debugger_tuple.js +15 -0
- package/dist/debug_server/tests/testModule/debugger_tuple.js.map +1 -0
- package/dist/debug_server/tests/testModule/import_failure.d.ts +1 -0
- package/dist/debug_server/tests/testModule/import_failure.js +4 -0
- package/dist/debug_server/tests/testModule/import_failure.js.map +1 -0
- package/dist/debug_server/tests/testModule/unittest_debug.case.js +10 -0
- package/dist/debug_server/tests/testModule/unittest_debug.case.js.map +1 -0
- package/dist/debug_server/tests/testModule/unittest_debug.d.ts +1 -0
- package/dist/debug_server/tests/testModule/unittest_debug.js +6 -0
- package/dist/debug_server/tests/testModule/unittest_debug.js.map +1 -0
- package/dist/debug_server/wasmEntry.js +32 -12
- package/dist/debug_server/wasmEntry.js.map +1 -1
- package/dist/dwarf/classDebugInfo.d.ts +45 -0
- package/dist/dwarf/classDebugInfo.js +292 -0
- package/dist/dwarf/classDebugInfo.js.map +1 -0
- package/dist/{heap_analyzer/src → dwarf}/dwarfParser.js +2 -0
- package/dist/dwarf/dwarfParser.js.map +1 -0
- package/dist/dwarf/functionDebugInfo.d.ts +63 -0
- package/dist/dwarf/functionDebugInfo.js +377 -0
- package/dist/dwarf/functionDebugInfo.js.map +1 -0
- package/dist/dwarf/tests/fixtures/functionDebugInfo.d.ts +3 -0
- package/dist/dwarf/tests/fixtures/functionDebugInfo.js +24 -0
- package/dist/dwarf/tests/fixtures/functionDebugInfo.js.map +1 -0
- package/dist/dwarf/tests/functionDebugInfo.test.d.ts +1 -0
- package/dist/dwarf/tests/functionDebugInfo.test.js +102 -0
- package/dist/dwarf/tests/functionDebugInfo.test.js.map +1 -0
- package/dist/heap_analyzer/cli.js +1 -1
- package/dist/heap_analyzer/cli.js.map +1 -1
- package/dist/heap_analyzer/src/constants.js +2 -1
- package/dist/heap_analyzer/src/constants.js.map +1 -1
- package/dist/heap_analyzer/src/debugInfoResolver.d.ts +4 -15
- package/dist/heap_analyzer/src/debugInfoResolver.js +20 -231
- package/dist/heap_analyzer/src/debugInfoResolver.js.map +1 -1
- package/dist/heap_analyzer/src/dominator.js +4 -1
- package/dist/heap_analyzer/src/dominator.js.map +1 -1
- package/dist/heap_analyzer/src/referenceScanner.js +7 -7
- package/dist/heap_analyzer/src/referenceScanner.js.map +1 -1
- package/dist/heap_analyzer/src/retainedSize.js +2 -0
- package/dist/heap_analyzer/src/retainedSize.js.map +1 -1
- package/dist/heap_analyzer/src/snapshot.js +1 -1
- package/dist/heap_analyzer/src/snapshot.js.map +1 -1
- package/dist/heap_analyzer/src/{tlsf.js → tlsfBlockWalker.js} +4 -3
- package/dist/heap_analyzer/src/tlsfBlockWalker.js.map +1 -0
- package/dist/heap_analyzer/src/types.d.ts +3 -32
- package/dist/heap_analyzer/src/types.js +1 -8
- package/dist/heap_analyzer/src/types.js.map +1 -1
- package/dist/heap_analyzer/tests/debugInfoResolver.test.js +1 -1
- package/dist/heap_analyzer/tests/debugInfoResolver.test.js.map +1 -1
- package/dist/heap_analyzer/tests/dwarfParser.test.js +57 -40
- package/dist/heap_analyzer/tests/dwarfParser.test.js.map +1 -1
- package/dist/heap_analyzer/tests/roots.test.js +1 -1
- package/dist/heap_analyzer/tests/roots.test.js.map +1 -1
- package/dist/heap_analyzer/tests/snapshot.test.js +4 -4
- package/dist/heap_analyzer/tests/testHelper.js +4 -7
- package/dist/heap_analyzer/tests/testHelper.js.map +1 -1
- package/dist/heap_analyzer/tests/tlsfBlockWalker.test.d.ts +1 -0
- package/dist/heap_analyzer/tests/{tlsf.test.js → tlsfBlockWalker.test.js} +2 -2
- package/dist/heap_analyzer/tests/tlsfBlockWalker.test.js.map +1 -0
- package/dist/heap_analyzer/tests/wasmExecutor.d.ts +2 -2
- package/dist/heap_analyzer/tests/wasmExecutor.js +5 -5
- package/dist/heap_analyzer/tests/wasmExecutor.js.map +1 -1
- package/dist/runtime/objectLayout.d.ts +10 -0
- package/dist/runtime/objectLayout.js +13 -0
- package/dist/runtime/objectLayout.js.map +1 -0
- package/dist/scripts/lib.d.ts +1 -0
- package/dist/scripts/lib.js +86 -22
- package/dist/scripts/lib.js.map +1 -1
- package/dist/test_runner/cli.d.ts +0 -1
- package/dist/test_runner/cli.js +37 -19
- package/dist/test_runner/cli.js.map +1 -1
- package/dist/test_runner/config.d.ts +3 -0
- package/dist/test_runner/config.js +8 -0
- package/dist/test_runner/config.js.map +1 -0
- package/dist/test_runner/core/analyze.js +12 -3
- package/dist/test_runner/core/analyze.js.map +1 -1
- package/dist/test_runner/core/compile.js +1 -4
- package/dist/test_runner/core/compile.js.map +1 -1
- package/dist/test_runner/core/compiler.js +2 -0
- package/dist/test_runner/core/compiler.js.map +1 -1
- package/dist/test_runner/core/execute.js +20 -3
- package/dist/test_runner/core/execute.js.map +1 -1
- package/dist/test_runner/executionResult.js +10 -8
- package/dist/test_runner/executionResult.js.map +1 -1
- package/dist/test_runner/generator/table-generator/index.js +4 -2
- package/dist/test_runner/generator/table-generator/index.js.map +1 -1
- package/dist/test_runner/parser/index.js +6 -5
- package/dist/test_runner/parser/index.js.map +1 -1
- package/dist/test_runner/parser/singleFileAnalysis.js +7 -4
- package/dist/test_runner/parser/singleFileAnalysis.js.map +1 -1
- package/dist/test_runner/parser/singleFunctionAnalysis.js +1 -1
- package/dist/test_runner/parser/singleFunctionAnalysis.js.map +1 -1
- package/dist/test_runner/utils/errorTraceHandler.js +2 -1
- package/dist/test_runner/utils/errorTraceHandler.js.map +1 -1
- package/dist/test_runner/utils/index.js +9 -2
- package/dist/test_runner/utils/index.js.map +1 -1
- package/dist/test_runner/utils/interface.js +2 -1
- package/dist/test_runner/utils/interface.js.map +1 -1
- package/dist/test_runner/utils/pathResolver.js +9 -7
- package/dist/test_runner/utils/pathResolver.js.map +1 -1
- package/dist/test_runner/utils/wasm.js +16 -8
- package/dist/test_runner/utils/wasm.js.map +1 -1
- package/dist/trace_visualizer.d.ts +2 -0
- package/dist/trace_visualizer.js +18 -0
- package/dist/trace_visualizer.js.map +1 -0
- package/dist/warpo.js +2 -1
- package/dist/warpo.js.map +1 -1
- package/package.json +9 -5
- package/dist/heap_analyzer/src/dwarfParser.js.map +0 -1
- package/dist/heap_analyzer/src/tlsf.js.map +0 -1
- package/dist/heap_analyzer/tests/tlsf.test.js.map +0 -1
- /package/dist/{heap_analyzer/tests/tlsf.test.d.ts → debug_server/tests/testModule/unittest_debug.case.d.ts} +0 -0
- /package/dist/{heap_analyzer/src → dwarf}/dwarfParser.d.ts +0 -0
- /package/dist/heap_analyzer/src/{tlsf.d.ts → tlsfBlockWalker.d.ts} +0 -0
|
@@ -1,30 +1,75 @@
|
|
|
1
1
|
// Copyright (C) 2025 wasm-ecosystem
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
import { LoggingDebugSession, InitializedEvent, Thread, Breakpoint, LoadedSourceEvent, StoppedEvent, ContinuedEvent, logger, } from "@vscode/debugadapter";
|
|
3
|
+
import { LoggingDebugSession, InitializedEvent, Thread, Breakpoint, Source, Scope, StackFrame, LoadedSourceEvent, OutputEvent, StoppedEvent, ContinuedEvent, TerminatedEvent, logger, } from "@vscode/debugadapter";
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
4
6
|
import * as path from "node:path";
|
|
7
|
+
import { BuiltinContainerKind, resolveArrayElementSize, } from "../dwarf/classDebugInfo.js";
|
|
8
|
+
import { ARRAY_DATA_START_OFFSET, ARRAY_LENGTH_OFFSET, AS_STRING_CLASS_NAME, OBJECT_RTID_OFFSET, OBJECT_RTID_SIZE, OBJECT_RTSIZE_OFFSET, OBJECT_RTSIZE_SIZE, SMALL_TUPLE_BITMAP_SIZE, SMALL_TUPLE_SLOT_SIZE, } from "../runtime/objectLayout.js";
|
|
9
|
+
import { normalizeDebugPath } from "./debugPath.js";
|
|
5
10
|
import { NodeDebugger } from "./nodeDebugger.js";
|
|
11
|
+
import { loadConfig } from "../test_runner/config.js";
|
|
12
|
+
function formatUnknownError(error) {
|
|
13
|
+
if (error instanceof Error) {
|
|
14
|
+
return error.message;
|
|
15
|
+
}
|
|
16
|
+
if (typeof error === "string") {
|
|
17
|
+
return error;
|
|
18
|
+
}
|
|
19
|
+
return "unknown error";
|
|
20
|
+
}
|
|
21
|
+
async function resolveUnitTestWasmPath(cwd) {
|
|
22
|
+
const configPath = path.join(cwd, "as-test.config.js");
|
|
23
|
+
let outputFolder = "coverage";
|
|
24
|
+
if (existsSync(configPath)) {
|
|
25
|
+
const config = await loadConfig(configPath);
|
|
26
|
+
if (typeof config.output === "string") {
|
|
27
|
+
outputFolder = config.output;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return path.resolve(cwd, outputFolder, "test.instrumented.wasm");
|
|
31
|
+
}
|
|
6
32
|
export class WarpoDebugSession extends LoggingDebugSession {
|
|
7
33
|
static threadId = 1;
|
|
8
34
|
breakpointId = 0;
|
|
9
35
|
requestedBreakpointsBySource = new Map();
|
|
10
|
-
|
|
36
|
+
pendingBreakpointSources = new Set();
|
|
37
|
+
variableContainers = new Map();
|
|
38
|
+
pausedStackFramesById = new Map();
|
|
39
|
+
nextVariablesReference = 1;
|
|
11
40
|
loadedModule;
|
|
12
41
|
runtime;
|
|
42
|
+
stoppedWasmBytecodeOffset;
|
|
43
|
+
stoppedForTrap = false;
|
|
44
|
+
debugSessionLogging = false;
|
|
13
45
|
log(msg) {
|
|
14
46
|
logger.log(msg);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (filePath === "") {
|
|
18
|
-
return "";
|
|
47
|
+
if (this.debugSessionLogging) {
|
|
48
|
+
this.sendEvent(new OutputEvent(`${msg}\n`, "stdout"));
|
|
19
49
|
}
|
|
20
|
-
return path.resolve(filePath).replaceAll("\\", "/");
|
|
21
50
|
}
|
|
22
51
|
sendStoppedEvent(runtime, info) {
|
|
23
52
|
this.log(`[${runtime.name}] Paused: ${info.reason}`);
|
|
24
|
-
|
|
53
|
+
this.clearVariableContainers();
|
|
54
|
+
let reason;
|
|
55
|
+
if (info.reason === "breakpoint") {
|
|
56
|
+
reason = "breakpoint";
|
|
57
|
+
}
|
|
58
|
+
else if (info.reason === "exception") {
|
|
59
|
+
reason = "exception";
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
reason = "pause";
|
|
63
|
+
}
|
|
64
|
+
this.log(`Sending stopped event: reason=${reason}, threadId=${WarpoDebugSession.threadId}`);
|
|
25
65
|
this.sendEvent(new StoppedEvent(reason, WarpoDebugSession.threadId, info.reason));
|
|
26
66
|
}
|
|
27
67
|
handleRuntimePause(runtime, info) {
|
|
68
|
+
this.stoppedWasmBytecodeOffset = info.wasmBytecodeOffset;
|
|
69
|
+
this.stoppedForTrap = info.reason === "exception";
|
|
70
|
+
if (info.reason === "other" && !this.loadedModule) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
28
73
|
this.applyPendingBreakpointUpdates(runtime, () => {
|
|
29
74
|
if (info.reason === "other") {
|
|
30
75
|
void runtime.resume().catch((error) => {
|
|
@@ -42,11 +87,12 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
42
87
|
response.body.supportsSetVariable = false;
|
|
43
88
|
response.body.supportsBreakpointLocationsRequest = false;
|
|
44
89
|
response.body.supportsLoadedSourcesRequest = true;
|
|
90
|
+
response.body.supportsEvaluateForHovers = false;
|
|
45
91
|
this.sendResponse(response);
|
|
46
92
|
this.sendEvent(new InitializedEvent());
|
|
47
93
|
}
|
|
48
94
|
setBreakPointsRequest(response, args) {
|
|
49
|
-
const sourcePath =
|
|
95
|
+
const sourcePath = normalizeDebugPath(args.source.path || "");
|
|
50
96
|
const clientLines = args.breakpoints || [];
|
|
51
97
|
const bps = clientLines.map((bp) => {
|
|
52
98
|
const id = ++this.breakpointId;
|
|
@@ -60,15 +106,23 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
60
106
|
return info;
|
|
61
107
|
});
|
|
62
108
|
this.requestedBreakpointsBySource.set(sourcePath, bps);
|
|
63
|
-
this.
|
|
109
|
+
this.pendingBreakpointSources.clear();
|
|
110
|
+
for (const pendingSourcePath of this.requestedBreakpointsBySource.keys()) {
|
|
111
|
+
this.pendingBreakpointSources.add(pendingSourcePath);
|
|
112
|
+
}
|
|
64
113
|
const breakpoints = bps.map((bp) => {
|
|
65
114
|
const dbp = new Breakpoint(bp.verified, bp.line);
|
|
66
115
|
dbp.id = bp.id;
|
|
67
116
|
return dbp;
|
|
68
117
|
});
|
|
69
118
|
response.body = { breakpoints };
|
|
70
|
-
this.
|
|
71
|
-
|
|
119
|
+
if (this.runtime?.isPaused() && this.loadedModule) {
|
|
120
|
+
this.requestBreakpointUpdate(() => this.sendResponse(response));
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
this.sendResponse(response);
|
|
124
|
+
this.requestBreakpointUpdate();
|
|
125
|
+
}
|
|
72
126
|
}
|
|
73
127
|
configurationDoneRequest(response, _args) {
|
|
74
128
|
super.configurationDoneRequest(response, _args);
|
|
@@ -79,9 +133,17 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
79
133
|
}
|
|
80
134
|
async doLaunchRequest(response, args) {
|
|
81
135
|
try {
|
|
82
|
-
this.
|
|
136
|
+
this.debugSessionLogging = args.debugSessionLogging ?? false;
|
|
83
137
|
const launchType = args.launchType ?? "wasm file";
|
|
84
|
-
|
|
138
|
+
const cwd = args.cwd ?? process.cwd();
|
|
139
|
+
const program = launchType === "unittest" ? await resolveUnitTestWasmPath(cwd) : args.program;
|
|
140
|
+
if (!program) {
|
|
141
|
+
this.sendErrorResponse(response, 1, "No program specified for launch");
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const wasmFilePath = path.resolve(program);
|
|
145
|
+
this.log(`Launch requested for: ${program}`);
|
|
146
|
+
if (launchType === "wasm file" || launchType === "unittest") {
|
|
85
147
|
const runtimeName = args.runtime ?? "node";
|
|
86
148
|
let runtime;
|
|
87
149
|
if (runtimeName === "node") {
|
|
@@ -91,34 +153,60 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
91
153
|
this.sendErrorResponse(response, 1, `Unknown runtime "${runtimeName}"`);
|
|
92
154
|
return;
|
|
93
155
|
}
|
|
94
|
-
runtime.onModuleLoad = (
|
|
95
|
-
this.loadedModule =
|
|
96
|
-
this.log(`Wasm module loaded: ${
|
|
156
|
+
runtime.onModuleLoad = (wasmModule) => {
|
|
157
|
+
this.loadedModule = wasmModule;
|
|
158
|
+
this.log(`Wasm module loaded: ${wasmModule.wasmFilePath} (reported as ${wasmModule.url}, scriptId: ${wasmModule.scriptId})`);
|
|
97
159
|
this.sendEvent(new LoadedSourceEvent("new", {
|
|
98
|
-
name: path.basename(
|
|
99
|
-
path:
|
|
160
|
+
name: path.basename(wasmModule.wasmFilePath),
|
|
161
|
+
path: wasmModule.wasmFilePath,
|
|
100
162
|
}));
|
|
101
163
|
this.sendEvent(new LoadedSourceEvent("new", {
|
|
102
|
-
name: path.basename(
|
|
103
|
-
path:
|
|
164
|
+
name: path.basename(wasmModule.sourceMapFilePath),
|
|
165
|
+
path: wasmModule.sourceMapFilePath,
|
|
104
166
|
}));
|
|
105
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
|
+
}
|
|
106
175
|
runtime.finishModuleLoad();
|
|
107
176
|
});
|
|
108
177
|
};
|
|
109
178
|
runtime.onPause = (info) => {
|
|
110
179
|
this.handleRuntimePause(runtime, info);
|
|
111
180
|
};
|
|
181
|
+
runtime.onRuntimeError = (message) => {
|
|
182
|
+
this.sendEvent(new OutputEvent(`${message}\n`, "stderr"));
|
|
183
|
+
};
|
|
184
|
+
runtime.onRuntimeExit = () => {
|
|
185
|
+
this.handleRuntimeExit(runtime);
|
|
186
|
+
};
|
|
187
|
+
runtime.onLog = (message) => {
|
|
188
|
+
this.log(message);
|
|
189
|
+
};
|
|
112
190
|
this.disposeLoadedModule();
|
|
113
191
|
this.runtime?.dispose();
|
|
114
192
|
this.runtime = runtime;
|
|
115
|
-
await runtime.launch(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
193
|
+
await runtime.launch(launchType === "unittest"
|
|
194
|
+
? {
|
|
195
|
+
wasmFilePath,
|
|
196
|
+
cwd,
|
|
197
|
+
warpoPath: args.warpoPath,
|
|
198
|
+
}
|
|
199
|
+
: {
|
|
200
|
+
wasmFilePath,
|
|
201
|
+
entryFunctionName: args.entryFunctionName ?? "main",
|
|
202
|
+
args: args.args ?? [],
|
|
203
|
+
});
|
|
120
204
|
this.log(`[${runtime.name}] Runtime launched`);
|
|
121
205
|
}
|
|
206
|
+
else {
|
|
207
|
+
this.sendErrorResponse(response, 1, `Unknown launch type "${launchType}"`);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
122
210
|
this.sendResponse(response);
|
|
123
211
|
}
|
|
124
212
|
catch (error) {
|
|
@@ -132,32 +220,793 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
132
220
|
};
|
|
133
221
|
this.sendResponse(response);
|
|
134
222
|
}
|
|
223
|
+
stackTraceRequest(response, args) {
|
|
224
|
+
this.doStackTraceRequest(response, args);
|
|
225
|
+
}
|
|
226
|
+
scopesRequest(response, args) {
|
|
227
|
+
this.doScopesRequest(response, args);
|
|
228
|
+
}
|
|
229
|
+
variablesRequest(response, args) {
|
|
230
|
+
void this.doVariablesRequest(response, args).catch((error) => {
|
|
231
|
+
const message = formatUnknownError(error);
|
|
232
|
+
this.log(`Variables request failed: ${message}`);
|
|
233
|
+
this.sendErrorResponse(response, 1, `Variables request failed: ${message}`);
|
|
234
|
+
});
|
|
235
|
+
}
|
|
135
236
|
continueRequest(response, _args) {
|
|
136
|
-
|
|
237
|
+
this.doContinueRequest(response);
|
|
137
238
|
}
|
|
138
239
|
disconnectRequest(response, _args) {
|
|
139
|
-
this.disposeLoadedModule();
|
|
140
|
-
this.pendingBreakpointUpdatesBySource.clear();
|
|
141
240
|
this.runtime?.dispose();
|
|
142
241
|
this.runtime = undefined;
|
|
242
|
+
this.resetRuntimeState();
|
|
243
|
+
this.debugSessionLogging = false;
|
|
244
|
+
this.dispose();
|
|
143
245
|
this.log("Debug session ended.");
|
|
144
246
|
this.sendResponse(response);
|
|
145
247
|
}
|
|
146
|
-
|
|
248
|
+
doStackTraceRequest(response, args) {
|
|
249
|
+
const runtime = this.runtime;
|
|
250
|
+
const frames = runtime ? runtime.getPausedWasmFrames() : [];
|
|
251
|
+
this.log(`StackTrace request: threadId=${args.threadId}, startFrame=${args.startFrame ?? 0}, levels=${args.levels ?? "all"}, runtimeFrames=${frames.length}`);
|
|
252
|
+
this.pausedStackFramesById.clear();
|
|
253
|
+
const stackFrames = frames.map((frame, index) => {
|
|
254
|
+
const id = index + 1;
|
|
255
|
+
this.pausedStackFramesById.set(id, { ...frame, frameIndex: index });
|
|
256
|
+
const sourceLocation = this.resolveSourceLocation(frame.wasmBytecodeOffset);
|
|
257
|
+
const sourceLabel = sourceLocation
|
|
258
|
+
? `${path.basename(sourceLocation.sourcePath)}:${sourceLocation.sourceLine}`
|
|
259
|
+
: "unknown";
|
|
260
|
+
this.log(` frame ${id}: index=${index}, name=${frame.functionName || "wasm"}, offset=${frame.wasmBytecodeOffset ?? "unknown"}, source=${sourceLabel}`);
|
|
261
|
+
const source = sourceLocation
|
|
262
|
+
? new Source(path.basename(sourceLocation.sourcePath), sourceLocation.sourcePath)
|
|
263
|
+
: undefined;
|
|
264
|
+
return new StackFrame(id, frame.functionName || "wasm", source, sourceLocation?.sourceLine ?? 0, 1);
|
|
265
|
+
});
|
|
266
|
+
const startFrame = args.startFrame ?? 0;
|
|
267
|
+
const requestedStackFrames = args.levels === undefined
|
|
268
|
+
? stackFrames.slice(startFrame)
|
|
269
|
+
: stackFrames.slice(startFrame, startFrame + args.levels);
|
|
270
|
+
response.body = {
|
|
271
|
+
stackFrames: requestedStackFrames,
|
|
272
|
+
totalFrames: stackFrames.length,
|
|
273
|
+
};
|
|
274
|
+
this.log(`StackTrace response: returned=${requestedStackFrames.length}, total=${stackFrames.length}`);
|
|
275
|
+
this.sendResponse(response);
|
|
276
|
+
}
|
|
277
|
+
resolveSourceLocation(wasmBytecodeOffset) {
|
|
278
|
+
const wasmModule = this.loadedModule;
|
|
279
|
+
if (!wasmModule || wasmBytecodeOffset === undefined) {
|
|
280
|
+
return undefined;
|
|
281
|
+
}
|
|
282
|
+
return wasmModule.resolveSourceLocation(wasmBytecodeOffset);
|
|
283
|
+
}
|
|
284
|
+
doScopesRequest(response, args) {
|
|
285
|
+
this.log(`Scopes request: frameId=${args.frameId}`);
|
|
286
|
+
const scopes = this.resolveLocalScopes(args.frameId);
|
|
287
|
+
response.body = { scopes };
|
|
288
|
+
this.sendResponse(response);
|
|
289
|
+
}
|
|
290
|
+
async doVariablesRequest(response, args) {
|
|
291
|
+
const variableContainer = this.variableContainers.get(args.variablesReference);
|
|
292
|
+
switch (variableContainer?.kind) {
|
|
293
|
+
case "locals": {
|
|
294
|
+
this.log(`Variables request: locals for frameId=${variableContainer.frameId}, scopeIndex=${variableContainer.scopeIndex}`);
|
|
295
|
+
response.body = {
|
|
296
|
+
variables: await this.resolveLocalScopeVariables(variableContainer.frameId, variableContainer.scopeIndex),
|
|
297
|
+
};
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
case "object": {
|
|
301
|
+
response.body = { variables: await this.decodeObjectAtAddress(variableContainer.address) };
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
case "map-entry": {
|
|
305
|
+
response.body = {
|
|
306
|
+
variables: [variableContainer.key, variableContainer.value].map((variable) => this.toDebugProtocolVariable(variable)),
|
|
307
|
+
};
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
default: {
|
|
311
|
+
this.log(`Warning: unknown variables reference ${args.variablesReference}`);
|
|
312
|
+
response.body = { variables: [] };
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
this.sendResponse(response);
|
|
317
|
+
}
|
|
318
|
+
resolveLocalScopes(frameId) {
|
|
319
|
+
const runtimeFrame = this.resolvePausedStackFrame(frameId);
|
|
320
|
+
const wasmModule = this.loadedModule;
|
|
321
|
+
if (!runtimeFrame || !wasmModule) {
|
|
322
|
+
this.log(`Scopes unavailable: frameId=${frameId}, hasFrame=${runtimeFrame !== undefined}, hasModule=${wasmModule !== undefined}`);
|
|
323
|
+
return [];
|
|
324
|
+
}
|
|
325
|
+
const wasmBytecodeOffset = runtimeFrame.wasmBytecodeOffset;
|
|
326
|
+
if (wasmBytecodeOffset === undefined) {
|
|
327
|
+
this.log(`Error: scopes unavailable: frame ${frameId} has no wasm bytecode offset`);
|
|
328
|
+
return [];
|
|
329
|
+
}
|
|
330
|
+
const sourceVariableScopes = wasmModule.getVariableScopesAtBytecodeOffset(wasmBytecodeOffset);
|
|
331
|
+
if (!sourceVariableScopes || sourceVariableScopes.length === 0) {
|
|
332
|
+
this.log(`Scopes response: frameId=${frameId}, no scopes`);
|
|
333
|
+
return [];
|
|
334
|
+
}
|
|
335
|
+
this.log(`Scopes response: frameId=${frameId}, scopes=${sourceVariableScopes.map((scope) => scope.name).join(", ")}`);
|
|
336
|
+
return sourceVariableScopes.map((scope, index) => new Scope(scope.name, this.createVariableContainer({ kind: "locals", frameId, scopeIndex: index }), false));
|
|
337
|
+
}
|
|
338
|
+
async resolveLocalScopeVariables(frameId, scopeIndex) {
|
|
339
|
+
const runtimeFrame = this.resolvePausedStackFrame(frameId);
|
|
340
|
+
const wasmModule = this.loadedModule;
|
|
341
|
+
const runtime = this.runtime;
|
|
342
|
+
if (!runtimeFrame || !wasmModule || !runtime) {
|
|
343
|
+
this.log(`Local variables unavailable: frameId=${frameId}, hasFrame=${runtimeFrame !== undefined}, hasModule=${wasmModule !== undefined}, hasRuntime=${runtime !== undefined}`);
|
|
344
|
+
return [];
|
|
345
|
+
}
|
|
346
|
+
const wasmBytecodeOffset = runtimeFrame.wasmBytecodeOffset;
|
|
347
|
+
if (wasmBytecodeOffset === undefined) {
|
|
348
|
+
this.log(`Error: local variables unavailable: frame ${frameId} has no wasm bytecode offset`);
|
|
349
|
+
return [];
|
|
350
|
+
}
|
|
351
|
+
const sourceVariableScopes = wasmModule.getVariableScopesAtBytecodeOffset(wasmBytecodeOffset);
|
|
352
|
+
if (!sourceVariableScopes) {
|
|
353
|
+
this.log(`Error: local variables unavailable: no source variables at wasm bytecode offset ${wasmBytecodeOffset}`);
|
|
354
|
+
return [];
|
|
355
|
+
}
|
|
356
|
+
const sourceVariableScope = sourceVariableScopes[scopeIndex];
|
|
357
|
+
if (!sourceVariableScope) {
|
|
358
|
+
this.log(`Error: local variables unavailable: no scope at index ${scopeIndex}`);
|
|
359
|
+
return [];
|
|
360
|
+
}
|
|
361
|
+
const runtimeVariables = await runtime.getPausedWasmFrameVariables(runtimeFrame.frameIndex);
|
|
362
|
+
this.log(`Local variables resolved: frameId=${frameId}, frameIndex=${runtimeFrame.frameIndex}, offset=${wasmBytecodeOffset}, scopeIndex=${scopeIndex}, scope=${sourceVariableScope.name}, runtimeVariables=${runtimeVariables.length}`);
|
|
363
|
+
const runtimeVariablesByIndex = new Map();
|
|
364
|
+
for (const variable of runtimeVariables) {
|
|
365
|
+
runtimeVariablesByIndex.set(variable.localIndex, variable);
|
|
366
|
+
}
|
|
367
|
+
const sourceVariables = sourceVariableScope.variables;
|
|
368
|
+
const rawWasmValues = await this.resolveRawWasmValues(sourceVariables, runtimeVariablesByIndex, runtime);
|
|
369
|
+
const closureTupleValuesByLevel = await this.readClosureTupleChain(sourceVariableScope.rootClosureEnvLocalIndex, runtimeVariablesByIndex);
|
|
370
|
+
const scopeTupleLevel = sourceVariableScope.tupleLevel ?? 0;
|
|
371
|
+
const rawLocalValues = sourceVariables.map((variable) => {
|
|
372
|
+
if (variable.kind === "closure") {
|
|
373
|
+
const elementIndex = variable.fieldOffset / SMALL_TUPLE_SLOT_SIZE;
|
|
374
|
+
const levelValues = closureTupleValuesByLevel.get(scopeTupleLevel);
|
|
375
|
+
const tupleRawValue = levelValues?.get(elementIndex);
|
|
376
|
+
if (tupleRawValue === undefined) {
|
|
377
|
+
return { name: variable.name, value: "<unavailable>", typeName: variable.typeName };
|
|
378
|
+
}
|
|
379
|
+
return { name: variable.name, value: tupleRawValue, typeName: variable.typeName };
|
|
380
|
+
}
|
|
381
|
+
const wasmRawValue = rawWasmValues.get(variable);
|
|
382
|
+
assert(wasmRawValue !== undefined);
|
|
383
|
+
return { name: variable.name, value: wasmRawValue, typeName: variable.typeName };
|
|
384
|
+
});
|
|
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
|
+
}
|
|
388
|
+
async decodeObjectAtAddress(address) {
|
|
389
|
+
const fields = await this.resolveObjectFields(address);
|
|
390
|
+
return fields.map((variable) => this.toDebugProtocolVariable(variable));
|
|
391
|
+
}
|
|
392
|
+
async resolveRawWasmValues(sourceVariables, runtimeVariablesByIndex, runtime) {
|
|
393
|
+
const rawWasmValues = new Map();
|
|
394
|
+
let runtimeGlobalsByIndex;
|
|
395
|
+
for (const variable of sourceVariables) {
|
|
396
|
+
if (variable.kind === "wasm-local") {
|
|
397
|
+
const runtimeVariable = runtimeVariablesByIndex.get(variable.localIndex);
|
|
398
|
+
assert(runtimeVariable !== undefined);
|
|
399
|
+
rawWasmValues.set(variable, runtimeVariable.value);
|
|
400
|
+
}
|
|
401
|
+
else if (variable.kind === "wasm-global") {
|
|
402
|
+
if (runtimeGlobalsByIndex === undefined) {
|
|
403
|
+
const pausedWasmGlobalVariables = await runtime.getPausedWasmGlobalVariables();
|
|
404
|
+
runtimeGlobalsByIndex = new Map(pausedWasmGlobalVariables.map((runtimeVariable) => [runtimeVariable.globalIndex, runtimeVariable.value]));
|
|
405
|
+
}
|
|
406
|
+
rawWasmValues.set(variable, runtimeGlobalsByIndex.get(variable.globalIndex) ?? "<unavailable>");
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return rawWasmValues;
|
|
410
|
+
}
|
|
411
|
+
async readClosureTupleChain(closureEnvLocalIndex, runtimeVariablesByIndex) {
|
|
412
|
+
const closureTupleValuesByLevel = new Map();
|
|
413
|
+
if (closureEnvLocalIndex === undefined) {
|
|
414
|
+
return closureTupleValuesByLevel;
|
|
415
|
+
}
|
|
416
|
+
const closureEnvVariable = runtimeVariablesByIndex.get(closureEnvLocalIndex);
|
|
417
|
+
if (closureEnvVariable === undefined) {
|
|
418
|
+
return closureTupleValuesByLevel;
|
|
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++;
|
|
442
|
+
}
|
|
443
|
+
return closureTupleValuesByLevel;
|
|
444
|
+
}
|
|
445
|
+
resolvePausedStackFrame(frameId) {
|
|
446
|
+
return this.pausedStackFramesById.get(frameId);
|
|
447
|
+
}
|
|
448
|
+
async toDebugSessionVariable(variable, name = variable.name, typeName = variable.type) {
|
|
449
|
+
return this.toDebugSessionVariableValue(name, typeName, variable.value);
|
|
450
|
+
}
|
|
451
|
+
async toDebugSessionVariableValue(name, typeName, value) {
|
|
452
|
+
if (typeName !== undefined) {
|
|
453
|
+
const objectAddress = this.parseObjectAddress(typeName, value);
|
|
454
|
+
if (objectAddress) {
|
|
455
|
+
if (typeName === AS_STRING_CLASS_NAME) {
|
|
456
|
+
const stringValue = await this.decodeStringAtAddress(typeName, objectAddress);
|
|
457
|
+
return this.createClassVariable(name, typeName, objectAddress, stringValue);
|
|
458
|
+
}
|
|
459
|
+
return this.createClassVariable(name, typeName, objectAddress);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return {
|
|
463
|
+
kind: "basic",
|
|
464
|
+
name,
|
|
465
|
+
value,
|
|
466
|
+
typeName,
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
toDebugProtocolVariable(variable, options = {}) {
|
|
470
|
+
const isExpandableClass = variable.kind === "class" && variable.displayValue === undefined;
|
|
471
|
+
const showClassNameInName = options.showClassNameInName ?? true;
|
|
472
|
+
return {
|
|
473
|
+
name: isExpandableClass && showClassNameInName ? `${variable.name}: ${variable.typeName}` : variable.name,
|
|
474
|
+
value: this.getVariableDisplayValue(variable),
|
|
475
|
+
type: variable.kind === "map-entry" ? undefined : variable.typeName,
|
|
476
|
+
variablesReference: this.getVariableReference(variable, isExpandableClass),
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
getVariableDisplayValue(variable) {
|
|
480
|
+
switch (variable.kind) {
|
|
481
|
+
case "basic": {
|
|
482
|
+
return variable.value;
|
|
483
|
+
}
|
|
484
|
+
case "class": {
|
|
485
|
+
return variable.displayValue ?? "";
|
|
486
|
+
}
|
|
487
|
+
case "map-entry": {
|
|
488
|
+
return "";
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
getVariableReference(variable, isExpandableClass) {
|
|
493
|
+
if (isExpandableClass && variable.kind === "class") {
|
|
494
|
+
return this.toObjectVariablesReference(variable.address);
|
|
495
|
+
}
|
|
496
|
+
if (variable.kind === "map-entry") {
|
|
497
|
+
return this.createVariableContainer({ kind: "map-entry", key: variable.key, value: variable.value });
|
|
498
|
+
}
|
|
499
|
+
return 0;
|
|
500
|
+
}
|
|
501
|
+
createClassVariable(name, typeName, address, displayValue) {
|
|
502
|
+
return { kind: "class", name, typeName, address, displayValue };
|
|
503
|
+
}
|
|
504
|
+
parseObjectAddress(typeName, value) {
|
|
505
|
+
if (!this.loadedModule?.getClassLayout(typeName)) {
|
|
506
|
+
return undefined;
|
|
507
|
+
}
|
|
508
|
+
const address = Number(value);
|
|
509
|
+
return Number.isInteger(address) && address > 0 ? address : undefined;
|
|
510
|
+
}
|
|
511
|
+
toObjectVariablesReference(address) {
|
|
512
|
+
return this.createVariableContainer({ kind: "object", address });
|
|
513
|
+
}
|
|
514
|
+
createVariableContainer(variableContainer) {
|
|
515
|
+
const variablesReference = this.nextVariablesReference++;
|
|
516
|
+
this.variableContainers.set(variablesReference, variableContainer);
|
|
517
|
+
return variablesReference;
|
|
518
|
+
}
|
|
519
|
+
clearVariableContainers() {
|
|
520
|
+
this.variableContainers.clear();
|
|
521
|
+
this.pausedStackFramesById.clear();
|
|
522
|
+
}
|
|
523
|
+
async readTupleElementValues(address, classLayout) {
|
|
524
|
+
if (!this.runtime) {
|
|
525
|
+
this.log(`Warning: cannot expand tuple at ${address}: runtime is not active`);
|
|
526
|
+
return [];
|
|
527
|
+
}
|
|
528
|
+
const tupleClassLayout = classLayout ?? (await this.resolveRuntimeClassLayout(address));
|
|
529
|
+
if (!tupleClassLayout) {
|
|
530
|
+
this.log(`Warning: cannot expand tuple at ${address}: runtime class layout is missing`);
|
|
531
|
+
return [];
|
|
532
|
+
}
|
|
533
|
+
if (tupleClassLayout.builtinKind !== BuiltinContainerKind.SmallTuple) {
|
|
534
|
+
this.log(`Warning: cannot expand tuple at ${address}: runtime class layout is not tuple`);
|
|
535
|
+
return [];
|
|
536
|
+
}
|
|
537
|
+
const payloadSize = await this.resolveRuntimeObjectPayloadSize(address, tupleClassLayout);
|
|
538
|
+
if (payloadSize === undefined) {
|
|
539
|
+
return [];
|
|
540
|
+
}
|
|
541
|
+
const memory = await this.runtime.readWasmMemory(address, payloadSize);
|
|
542
|
+
if (!memory) {
|
|
543
|
+
this.log(`Warning: cannot expand tuple at ${address}: tuple memory is unavailable`);
|
|
544
|
+
return [];
|
|
545
|
+
}
|
|
546
|
+
const view = new DataView(memory.buffer, memory.byteOffset, memory.byteLength);
|
|
547
|
+
assert.equal(tupleClassLayout.builtinKind, BuiltinContainerKind.SmallTuple);
|
|
548
|
+
if (view.byteLength <= SMALL_TUPLE_BITMAP_SIZE) {
|
|
549
|
+
return [];
|
|
550
|
+
}
|
|
551
|
+
const elementAreaSize = view.byteLength - SMALL_TUPLE_BITMAP_SIZE;
|
|
552
|
+
if (elementAreaSize % SMALL_TUPLE_SLOT_SIZE !== 0) {
|
|
553
|
+
this.log(`Error: cannot expand tuple at ${address}: tuple element area is not slot aligned`);
|
|
554
|
+
return [];
|
|
555
|
+
}
|
|
556
|
+
const bitmap = view.getBigUint64(elementAreaSize, true);
|
|
557
|
+
const values = [];
|
|
558
|
+
for (let index = 0; index < elementAreaSize / SMALL_TUPLE_SLOT_SIZE; index++) {
|
|
559
|
+
const offset = index * SMALL_TUPLE_SLOT_SIZE;
|
|
560
|
+
const isReference = (bitmap & (1n << BigInt(index))) !== 0n;
|
|
561
|
+
const value = isReference
|
|
562
|
+
? this.decodeTupleReferenceValue(view, offset)
|
|
563
|
+
: this.decodeTupleBasicValue(view, offset);
|
|
564
|
+
values.push({ value, isReference });
|
|
565
|
+
}
|
|
566
|
+
return values;
|
|
567
|
+
}
|
|
568
|
+
async decodeTupleElements(address, classLayout) {
|
|
569
|
+
const values = await this.readTupleElementValues(address, classLayout);
|
|
570
|
+
return Promise.all(values.map((value, index) => value.isReference
|
|
571
|
+
? this.decodeTupleReferenceElement(index.toString(), value.value)
|
|
572
|
+
: this.toDebugSessionVariableValue(index.toString(), "usize", value.value)));
|
|
573
|
+
}
|
|
574
|
+
async resolveObjectFields(address) {
|
|
575
|
+
const wasmModule = this.loadedModule;
|
|
576
|
+
if (!wasmModule) {
|
|
577
|
+
this.log(`Warning: cannot expand object at ${address}: wasm module is not loaded`);
|
|
578
|
+
return [];
|
|
579
|
+
}
|
|
580
|
+
if (!this.runtime) {
|
|
581
|
+
this.log(`Warning: cannot expand object at ${address}: runtime is not active`);
|
|
582
|
+
return [];
|
|
583
|
+
}
|
|
584
|
+
const classLayout = await this.resolveRuntimeClassLayout(address);
|
|
585
|
+
if (!classLayout) {
|
|
586
|
+
this.log(`Warning: cannot expand object at ${address}: runtime class layout is missing`);
|
|
587
|
+
return [];
|
|
588
|
+
}
|
|
589
|
+
if (classLayout.builtinKind === BuiltinContainerKind.SmallTuple) {
|
|
590
|
+
return this.decodeTupleElements(address, classLayout);
|
|
591
|
+
}
|
|
592
|
+
const payloadSize = await this.resolveRuntimeObjectPayloadSize(address, classLayout);
|
|
593
|
+
if (payloadSize === undefined) {
|
|
594
|
+
return [];
|
|
595
|
+
}
|
|
596
|
+
const memory = await this.runtime.readWasmMemory(address, payloadSize);
|
|
597
|
+
if (!memory) {
|
|
598
|
+
this.log(`Warning: cannot expand object at ${address}: object memory is unavailable`);
|
|
599
|
+
return [];
|
|
600
|
+
}
|
|
601
|
+
const view = new DataView(memory.buffer, memory.byteOffset, memory.byteLength);
|
|
602
|
+
if (classLayout.builtinKind === BuiltinContainerKind.ArrayBuffer) {
|
|
603
|
+
return this.decodeArrayBufferElements(view);
|
|
604
|
+
}
|
|
605
|
+
if (classLayout.builtinKind === BuiltinContainerKind.Array) {
|
|
606
|
+
return this.decodeArrayElements(address, view, classLayout);
|
|
607
|
+
}
|
|
608
|
+
if (classLayout.builtinKind === BuiltinContainerKind.StaticArray) {
|
|
609
|
+
return this.decodeStaticArrayElements(address, view, classLayout);
|
|
610
|
+
}
|
|
611
|
+
if (this.isSetClassLayout(classLayout)) {
|
|
612
|
+
return this.decodeSetElements(address, view, classLayout);
|
|
613
|
+
}
|
|
614
|
+
if (this.isMapClassLayout(classLayout)) {
|
|
615
|
+
return this.decodeMapElements(address, view, classLayout);
|
|
616
|
+
}
|
|
617
|
+
return Promise.all(classLayout.fields.map((field) => this.decodeFieldVariable(view, field)));
|
|
618
|
+
}
|
|
619
|
+
async resolveRuntimeObjectPayloadSize(address, classLayout) {
|
|
620
|
+
if (classLayout.builtinKind !== BuiltinContainerKind.SmallTuple &&
|
|
621
|
+
classLayout.builtinKind !== BuiltinContainerKind.StaticArray &&
|
|
622
|
+
classLayout.builtinKind !== BuiltinContainerKind.ArrayBuffer) {
|
|
623
|
+
return classLayout.byteSize;
|
|
624
|
+
}
|
|
625
|
+
const sizeMemory = await this.runtime?.readWasmMemory(address + OBJECT_RTSIZE_OFFSET, OBJECT_RTSIZE_SIZE);
|
|
626
|
+
if (!sizeMemory) {
|
|
627
|
+
this.log(`Warning: cannot read object size at ${address + OBJECT_RTSIZE_OFFSET}`);
|
|
628
|
+
return undefined;
|
|
629
|
+
}
|
|
630
|
+
const sizeView = new DataView(sizeMemory.buffer, sizeMemory.byteOffset, sizeMemory.byteLength);
|
|
631
|
+
return sizeView.getUint32(0, true);
|
|
632
|
+
}
|
|
633
|
+
decodeTupleReferenceValue(view, offset) {
|
|
634
|
+
if (offset + SMALL_TUPLE_SLOT_SIZE > view.byteLength) {
|
|
635
|
+
return "<unavailable>";
|
|
636
|
+
}
|
|
637
|
+
const elementAddress = view.getUint32(offset, true);
|
|
638
|
+
return elementAddress === 0 ? "null" : elementAddress.toString();
|
|
639
|
+
}
|
|
640
|
+
decodeTupleBasicValue(view, offset) {
|
|
641
|
+
return this.decodeFieldValue(view, {
|
|
642
|
+
name: "",
|
|
643
|
+
typeName: "usize",
|
|
644
|
+
offset,
|
|
645
|
+
size: SMALL_TUPLE_SLOT_SIZE,
|
|
646
|
+
isReference: false,
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
async decodeTupleReferenceElement(name, value) {
|
|
650
|
+
const elementAddress = Number(value);
|
|
651
|
+
if (elementAddress === 0) {
|
|
652
|
+
return { kind: "basic", name, value: "null", typeName: undefined };
|
|
653
|
+
}
|
|
654
|
+
if (!Number.isInteger(elementAddress) || elementAddress <= 0) {
|
|
655
|
+
return { kind: "basic", name, value, typeName: undefined };
|
|
656
|
+
}
|
|
657
|
+
const classLayout = await this.resolveRuntimeClassLayout(elementAddress);
|
|
658
|
+
if (!classLayout) {
|
|
659
|
+
this.log(`Error: cannot expand tuple reference at ${elementAddress}: runtime class layout is missing`);
|
|
660
|
+
return { kind: "basic", name, value: elementAddress.toString(), typeName: undefined };
|
|
661
|
+
}
|
|
662
|
+
if (classLayout.name === AS_STRING_CLASS_NAME) {
|
|
663
|
+
const stringValue = await this.decodeStringAtAddress(classLayout.name, elementAddress);
|
|
664
|
+
return this.createClassVariable(name, classLayout.name, elementAddress, stringValue);
|
|
665
|
+
}
|
|
666
|
+
return this.createClassVariable(name, classLayout.name, elementAddress);
|
|
667
|
+
}
|
|
668
|
+
async decodeArrayElements(address, view, classLayout) {
|
|
669
|
+
assert.equal(classLayout.builtinKind, BuiltinContainerKind.Array);
|
|
670
|
+
const elementTypeName = classLayout.templateType;
|
|
671
|
+
if (elementTypeName === undefined) {
|
|
672
|
+
this.log(`Error: cannot expand array at ${address}: element type is unavailable`);
|
|
673
|
+
return [];
|
|
674
|
+
}
|
|
675
|
+
if (ARRAY_LENGTH_OFFSET + 4 > view.byteLength) {
|
|
676
|
+
this.log(`Error: cannot expand array at ${address}: array header is unavailable`);
|
|
677
|
+
return [];
|
|
678
|
+
}
|
|
679
|
+
const dataStart = view.getUint32(ARRAY_DATA_START_OFFSET, true);
|
|
680
|
+
const length = view.getUint32(ARRAY_LENGTH_OFFSET, true);
|
|
681
|
+
const elementSize = resolveArrayElementSize(elementTypeName, classLayout.templateTypeIsReference === true);
|
|
682
|
+
const elementsMemory = await this.runtime?.readWasmMemory(dataStart, length * elementSize);
|
|
683
|
+
if (!elementsMemory) {
|
|
684
|
+
this.log(`Error: cannot expand array at ${address}: array elements memory is unavailable`);
|
|
685
|
+
return [];
|
|
686
|
+
}
|
|
687
|
+
const elementsView = new DataView(elementsMemory.buffer, elementsMemory.byteOffset, elementsMemory.byteLength);
|
|
688
|
+
return this.decodeArrayElementVariables(elementsView, length, elementTypeName, elementSize, classLayout.templateTypeIsReference === true);
|
|
689
|
+
}
|
|
690
|
+
decodeArrayBufferElements(view) {
|
|
691
|
+
const elements = [];
|
|
692
|
+
for (let index = 0; index < view.byteLength; index++) {
|
|
693
|
+
elements.push({
|
|
694
|
+
kind: "basic",
|
|
695
|
+
name: index.toString(),
|
|
696
|
+
value: view.getUint8(index).toString(),
|
|
697
|
+
typeName: "u8",
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
return elements;
|
|
701
|
+
}
|
|
702
|
+
async decodeStaticArrayElements(address, view, classLayout) {
|
|
703
|
+
assert.equal(classLayout.builtinKind, BuiltinContainerKind.StaticArray);
|
|
704
|
+
const elementTypeName = classLayout.templateType;
|
|
705
|
+
if (elementTypeName === undefined) {
|
|
706
|
+
this.log(`Error: cannot expand static array at ${address}: element type is unavailable`);
|
|
707
|
+
return [];
|
|
708
|
+
}
|
|
709
|
+
const elementSize = resolveArrayElementSize(elementTypeName, classLayout.templateTypeIsReference === true);
|
|
710
|
+
if (view.byteLength % elementSize !== 0) {
|
|
711
|
+
this.log(`Error: cannot expand static array at ${address}: payload size is not element aligned`);
|
|
712
|
+
return [];
|
|
713
|
+
}
|
|
714
|
+
return this.decodeArrayElementVariables(view, view.byteLength / elementSize, elementTypeName, elementSize, classLayout.templateTypeIsReference === true);
|
|
715
|
+
}
|
|
716
|
+
async decodeArrayElementVariables(view, length, elementTypeName, elementSize, isReference) {
|
|
717
|
+
const elementVariables = [];
|
|
718
|
+
for (let index = 0; index < length; index++) {
|
|
719
|
+
elementVariables.push(this.decodeFieldVariable(view, {
|
|
720
|
+
name: index.toString(),
|
|
721
|
+
typeName: elementTypeName,
|
|
722
|
+
offset: index * elementSize,
|
|
723
|
+
size: elementSize,
|
|
724
|
+
isReference,
|
|
725
|
+
}));
|
|
726
|
+
}
|
|
727
|
+
return Promise.all(elementVariables);
|
|
728
|
+
}
|
|
729
|
+
async decodeSetElements(address, view, classLayout) {
|
|
730
|
+
const elementTypeName = classLayout.templateType;
|
|
731
|
+
if (elementTypeName === undefined) {
|
|
732
|
+
this.log(`Error: cannot expand set at ${address}: element type is unavailable`);
|
|
733
|
+
return [];
|
|
734
|
+
}
|
|
735
|
+
const entryLayout = classLayout.entryLayout;
|
|
736
|
+
if (entryLayout === undefined) {
|
|
737
|
+
this.log(`Error: cannot expand set at ${address}: entry layout is unavailable`);
|
|
738
|
+
return [];
|
|
739
|
+
}
|
|
740
|
+
const entriesView = await this.readMapOrSetEntries(address, view, entryLayout.size, "set");
|
|
741
|
+
if (!entriesView) {
|
|
742
|
+
return [];
|
|
743
|
+
}
|
|
744
|
+
const entriesOffset = view.getInt32(16, true);
|
|
745
|
+
if (entriesOffset <= 0) {
|
|
746
|
+
return [];
|
|
747
|
+
}
|
|
748
|
+
const elementSize = resolveArrayElementSize(elementTypeName, classLayout.templateTypeIsReference === true);
|
|
749
|
+
const elements = [];
|
|
750
|
+
for (let entryIndex = 0; entryIndex < entriesOffset; entryIndex++) {
|
|
751
|
+
const entryBase = entryIndex * entryLayout.size;
|
|
752
|
+
const taggedNextOffset = entryBase + entryLayout.size - 4;
|
|
753
|
+
if (taggedNextOffset + 4 > entriesView.byteLength) {
|
|
754
|
+
break;
|
|
755
|
+
}
|
|
756
|
+
const taggedNext = entriesView.getUint32(taggedNextOffset, true);
|
|
757
|
+
if (taggedNext & 1) {
|
|
758
|
+
continue;
|
|
759
|
+
}
|
|
760
|
+
elements.push(this.decodeFieldVariable(entriesView, {
|
|
761
|
+
name: elements.length.toString(),
|
|
762
|
+
typeName: elementTypeName,
|
|
763
|
+
offset: entryBase,
|
|
764
|
+
size: elementSize,
|
|
765
|
+
isReference: classLayout.templateTypeIsReference === true,
|
|
766
|
+
}));
|
|
767
|
+
}
|
|
768
|
+
return Promise.all(elements);
|
|
769
|
+
}
|
|
770
|
+
async decodeMapElements(address, view, classLayout) {
|
|
771
|
+
const entryLayout = classLayout.entryLayout;
|
|
772
|
+
if (entryLayout === undefined) {
|
|
773
|
+
this.log(`Error: cannot expand map at ${address}: entry layout is unavailable`);
|
|
774
|
+
return [];
|
|
775
|
+
}
|
|
776
|
+
const entryClass = this.getMapOrSetEntryClassLayout(classLayout.name);
|
|
777
|
+
const keyField = entryClass?.fields.find((field) => field.name === "key");
|
|
778
|
+
const valueField = entryClass?.fields.find((field) => field.name === "value");
|
|
779
|
+
if (!keyField || !valueField) {
|
|
780
|
+
this.log(`Error: cannot expand map at ${address}: key/value entry fields are unavailable`);
|
|
781
|
+
return [];
|
|
782
|
+
}
|
|
783
|
+
const entriesView = await this.readMapOrSetEntries(address, view, entryLayout.size, "map");
|
|
784
|
+
if (!entriesView) {
|
|
785
|
+
return [];
|
|
786
|
+
}
|
|
787
|
+
const entriesOffset = view.getInt32(16, true);
|
|
788
|
+
const elements = [];
|
|
789
|
+
for (let entryIndex = 0; entryIndex < entriesOffset; entryIndex++) {
|
|
790
|
+
const entryBase = entryIndex * entryLayout.size;
|
|
791
|
+
const taggedNextOffset = entryBase + entryLayout.size - 4;
|
|
792
|
+
if (taggedNextOffset + 4 > entriesView.byteLength) {
|
|
793
|
+
break;
|
|
794
|
+
}
|
|
795
|
+
const taggedNext = entriesView.getUint32(taggedNextOffset, true);
|
|
796
|
+
if (taggedNext & 1) {
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
799
|
+
elements.push(this.decodeMapEntry(entriesView, elements.length.toString(), entryBase, keyField, valueField));
|
|
800
|
+
}
|
|
801
|
+
return Promise.all(elements);
|
|
802
|
+
}
|
|
803
|
+
isSetClassLayout(classLayout) {
|
|
804
|
+
return classLayout.name.startsWith("~lib/set/Set<");
|
|
805
|
+
}
|
|
806
|
+
isMapClassLayout(classLayout) {
|
|
807
|
+
return classLayout.name.startsWith("~lib/map/Map<");
|
|
808
|
+
}
|
|
809
|
+
getMapOrSetEntryClassLayout(className) {
|
|
810
|
+
if (className.startsWith("~lib/set/Set<")) {
|
|
811
|
+
return this.loadedModule?.getInternalClassLayout(className.replace("~lib/set/Set<", "~lib/set/SetEntry<"));
|
|
812
|
+
}
|
|
813
|
+
if (className.startsWith("~lib/map/Map<")) {
|
|
814
|
+
return this.loadedModule?.getInternalClassLayout(className.replace("~lib/map/Map<", "~lib/map/MapEntry<"));
|
|
815
|
+
}
|
|
816
|
+
return undefined;
|
|
817
|
+
}
|
|
818
|
+
async decodeMapEntry(entriesView, name, entryBase, keyField, valueField) {
|
|
819
|
+
const [key, value] = await Promise.all([
|
|
820
|
+
this.decodeFieldVariable(entriesView, { ...keyField, name: "key", offset: entryBase + keyField.offset }),
|
|
821
|
+
this.decodeFieldVariable(entriesView, { ...valueField, name: "value", offset: entryBase + valueField.offset }),
|
|
822
|
+
]);
|
|
823
|
+
return { kind: "map-entry", name, key, value };
|
|
824
|
+
}
|
|
825
|
+
async readMapOrSetEntries(address, view, entrySize, containerKind) {
|
|
826
|
+
if (view.byteLength < 20) {
|
|
827
|
+
this.log(`Error: cannot expand ${containerKind} at ${address}: ${containerKind} header is unavailable`);
|
|
828
|
+
return undefined;
|
|
829
|
+
}
|
|
830
|
+
const entriesPtr = view.getUint32(8, true);
|
|
831
|
+
const entriesOffset = view.getInt32(16, true);
|
|
832
|
+
if (entriesPtr === 0 || entriesOffset <= 0) {
|
|
833
|
+
return new DataView(new ArrayBuffer(0));
|
|
834
|
+
}
|
|
835
|
+
const entriesMemory = await this.runtime?.readWasmMemory(entriesPtr, entriesOffset * entrySize);
|
|
836
|
+
if (!entriesMemory) {
|
|
837
|
+
this.log(`Error: cannot expand ${containerKind} at ${address}: ${containerKind} entries memory is unavailable`);
|
|
838
|
+
return undefined;
|
|
839
|
+
}
|
|
840
|
+
return new DataView(entriesMemory.buffer, entriesMemory.byteOffset, entriesMemory.byteLength);
|
|
841
|
+
}
|
|
842
|
+
async resolveRuntimeClassLayout(address) {
|
|
843
|
+
const header = await this.runtime?.readWasmMemory(address + OBJECT_RTID_OFFSET, OBJECT_RTID_SIZE);
|
|
844
|
+
if (!header) {
|
|
845
|
+
this.log(`Warning: cannot read object RTID at ${address + OBJECT_RTID_OFFSET}`);
|
|
846
|
+
return undefined;
|
|
847
|
+
}
|
|
848
|
+
const headerView = new DataView(header.buffer, header.byteOffset, header.byteLength);
|
|
849
|
+
const rtid = headerView.getUint32(0, true);
|
|
850
|
+
const classLayout = this.loadedModule?.getClassLayoutByRtid(rtid);
|
|
851
|
+
if (!classLayout) {
|
|
852
|
+
this.log(`Warning: cannot resolve class layout for RTID ${rtid}`);
|
|
853
|
+
}
|
|
854
|
+
return classLayout;
|
|
855
|
+
}
|
|
856
|
+
async decodeFieldVariable(view, field) {
|
|
857
|
+
const value = this.decodeFieldValue(view, field);
|
|
858
|
+
const address = field.isReference ? this.parseObjectAddress(field.typeName, value) : undefined;
|
|
859
|
+
if (address) {
|
|
860
|
+
if (field.typeName === AS_STRING_CLASS_NAME) {
|
|
861
|
+
const stringValue = await this.decodeStringAtAddress(field.typeName, address);
|
|
862
|
+
return this.createClassVariable(field.name, field.typeName, address, stringValue);
|
|
863
|
+
}
|
|
864
|
+
return this.createClassVariable(field.name, field.typeName, address);
|
|
865
|
+
}
|
|
866
|
+
return {
|
|
867
|
+
kind: "basic",
|
|
868
|
+
name: field.name,
|
|
869
|
+
value,
|
|
870
|
+
typeName: field.typeName,
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
decodeFieldValue(view, field) {
|
|
874
|
+
if (field.offset + field.size > view.byteLength) {
|
|
875
|
+
return "<unavailable>";
|
|
876
|
+
}
|
|
877
|
+
if (field.isReference) {
|
|
878
|
+
const address = view.getUint32(field.offset, true);
|
|
879
|
+
return address === 0 ? "null" : address.toString();
|
|
880
|
+
}
|
|
881
|
+
switch (field.typeName) {
|
|
882
|
+
case "bool": {
|
|
883
|
+
return String(view.getUint8(field.offset) !== 0);
|
|
884
|
+
}
|
|
885
|
+
case "i8": {
|
|
886
|
+
return view.getInt8(field.offset).toString();
|
|
887
|
+
}
|
|
888
|
+
case "u8": {
|
|
889
|
+
return view.getUint8(field.offset).toString();
|
|
890
|
+
}
|
|
891
|
+
case "i16": {
|
|
892
|
+
return view.getInt16(field.offset, true).toString();
|
|
893
|
+
}
|
|
894
|
+
case "u16": {
|
|
895
|
+
return view.getUint16(field.offset, true).toString();
|
|
896
|
+
}
|
|
897
|
+
case "i32":
|
|
898
|
+
case "isize": {
|
|
899
|
+
return view.getInt32(field.offset, true).toString();
|
|
900
|
+
}
|
|
901
|
+
case "u32":
|
|
902
|
+
case "usize": {
|
|
903
|
+
return view.getUint32(field.offset, true).toString();
|
|
904
|
+
}
|
|
905
|
+
case "i64": {
|
|
906
|
+
return view.getBigInt64(field.offset, true).toString();
|
|
907
|
+
}
|
|
908
|
+
case "u64": {
|
|
909
|
+
return view.getBigUint64(field.offset, true).toString();
|
|
910
|
+
}
|
|
911
|
+
case "f32": {
|
|
912
|
+
return view.getFloat32(field.offset, true).toString();
|
|
913
|
+
}
|
|
914
|
+
case "f64": {
|
|
915
|
+
return view.getFloat64(field.offset, true).toString();
|
|
916
|
+
}
|
|
917
|
+
default: {
|
|
918
|
+
return this.decodeIntegerBySize(view, field);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
decodeIntegerBySize(view, field) {
|
|
923
|
+
switch (field.size) {
|
|
924
|
+
case 1: {
|
|
925
|
+
return view.getUint8(field.offset).toString();
|
|
926
|
+
}
|
|
927
|
+
case 2: {
|
|
928
|
+
return view.getUint16(field.offset, true).toString();
|
|
929
|
+
}
|
|
930
|
+
case 4: {
|
|
931
|
+
return view.getUint32(field.offset, true).toString();
|
|
932
|
+
}
|
|
933
|
+
case 8: {
|
|
934
|
+
return view.getBigUint64(field.offset, true).toString();
|
|
935
|
+
}
|
|
936
|
+
default: {
|
|
937
|
+
return "<unavailable>";
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
async decodeStringAtAddress(typeName, address) {
|
|
942
|
+
assert.equal(typeName, AS_STRING_CLASS_NAME);
|
|
147
943
|
if (!this.runtime) {
|
|
944
|
+
this.log(`Error: cannot decode string at ${address}: runtime is not active`);
|
|
945
|
+
return "<unavailable>";
|
|
946
|
+
}
|
|
947
|
+
const sizeMemory = await this.runtime.readWasmMemory(address + OBJECT_RTSIZE_OFFSET, OBJECT_RTSIZE_SIZE);
|
|
948
|
+
if (!sizeMemory) {
|
|
949
|
+
this.log(`Error: cannot decode string at ${address}: string size memory is unavailable`);
|
|
950
|
+
return "<unavailable>";
|
|
951
|
+
}
|
|
952
|
+
const sizeView = new DataView(sizeMemory.buffer, sizeMemory.byteOffset, sizeMemory.byteLength);
|
|
953
|
+
const byteLength = sizeView.getUint32(0, true);
|
|
954
|
+
const stringMemory = await this.runtime.readWasmMemory(address, byteLength);
|
|
955
|
+
if (!stringMemory) {
|
|
956
|
+
this.log(`Error: cannot decode string at ${address}: string payload memory is unavailable`);
|
|
957
|
+
return "<unavailable>";
|
|
958
|
+
}
|
|
959
|
+
return JSON.stringify(Buffer.from(stringMemory).toString("utf16le"));
|
|
960
|
+
}
|
|
961
|
+
doContinueRequest(response) {
|
|
962
|
+
const runtime = this.runtime;
|
|
963
|
+
if (!runtime) {
|
|
148
964
|
this.sendErrorResponse(response, 1, "No active runtime");
|
|
149
965
|
return;
|
|
150
966
|
}
|
|
967
|
+
response.body = { allThreadsContinued: true };
|
|
968
|
+
this.sendResponse(response);
|
|
969
|
+
if (this.stoppedForTrap) {
|
|
970
|
+
this.log("Continue requested from wasm trap; terminating debug session");
|
|
971
|
+
this.disposeRuntimeAndTerminate(runtime);
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
void this.resumeRuntime(runtime);
|
|
975
|
+
}
|
|
976
|
+
async resumeRuntime(runtime) {
|
|
151
977
|
try {
|
|
152
|
-
await
|
|
153
|
-
response.body = { allThreadsContinued: true };
|
|
154
|
-
this.sendResponse(response);
|
|
155
|
-
this.sendEvent(new ContinuedEvent(WarpoDebugSession.threadId, true));
|
|
978
|
+
await runtime.resume();
|
|
156
979
|
}
|
|
157
980
|
catch (error) {
|
|
158
|
-
|
|
159
|
-
|
|
981
|
+
this.log(`Continue failed: ${formatUnknownError(error)}`);
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
if (this.runtime !== runtime) {
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
this.clearVariableContainers();
|
|
988
|
+
this.stoppedWasmBytecodeOffset = undefined;
|
|
989
|
+
this.sendEvent(new ContinuedEvent(WarpoDebugSession.threadId, true));
|
|
990
|
+
}
|
|
991
|
+
handleRuntimeExit(runtime) {
|
|
992
|
+
if (this.runtime !== runtime) {
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
this.disposeRuntimeAndTerminate(runtime);
|
|
996
|
+
}
|
|
997
|
+
disposeRuntimeAndTerminate(runtime) {
|
|
998
|
+
runtime.dispose();
|
|
999
|
+
if (this.runtime === runtime) {
|
|
1000
|
+
this.runtime = undefined;
|
|
160
1001
|
}
|
|
1002
|
+
this.resetRuntimeState();
|
|
1003
|
+
this.sendEvent(new TerminatedEvent());
|
|
1004
|
+
}
|
|
1005
|
+
resetRuntimeState() {
|
|
1006
|
+
this.disposeLoadedModule();
|
|
1007
|
+
this.pendingBreakpointSources.clear();
|
|
1008
|
+
this.stoppedWasmBytecodeOffset = undefined;
|
|
1009
|
+
this.stoppedForTrap = false;
|
|
161
1010
|
}
|
|
162
1011
|
logAllBreakpoints() {
|
|
163
1012
|
this.log("=== Registered Breakpoints ===");
|
|
@@ -168,64 +1017,54 @@ export class WarpoDebugSession extends LoggingDebugSession {
|
|
|
168
1017
|
}
|
|
169
1018
|
this.log("==============================");
|
|
170
1019
|
}
|
|
171
|
-
requestBreakpointUpdate() {
|
|
172
|
-
if (!this.runtime || !this.loadedModule || this.
|
|
1020
|
+
requestBreakpointUpdate(onComplete) {
|
|
1021
|
+
if (!this.runtime || !this.loadedModule || this.pendingBreakpointSources.size === 0) {
|
|
1022
|
+
onComplete?.();
|
|
173
1023
|
return;
|
|
174
1024
|
}
|
|
175
1025
|
if (this.runtime.isPaused()) {
|
|
176
|
-
this.applyPendingBreakpointUpdates(this.runtime);
|
|
1026
|
+
this.applyPendingBreakpointUpdates(this.runtime, onComplete);
|
|
177
1027
|
return;
|
|
178
1028
|
}
|
|
179
|
-
|
|
180
|
-
if (this.loadedModule.hasSource(sourcePath)) {
|
|
181
|
-
this.runtime.pause();
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
1029
|
+
this.runtime.pause();
|
|
185
1030
|
}
|
|
186
1031
|
applyPendingBreakpointUpdates(runtime, onComplete) {
|
|
187
|
-
const
|
|
188
|
-
if (!
|
|
1032
|
+
const wasmModule = this.loadedModule;
|
|
1033
|
+
if (!wasmModule) {
|
|
189
1034
|
onComplete?.();
|
|
190
1035
|
return;
|
|
191
1036
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
this.pendingBreakpointUpdatesBySource.delete(sourcePath);
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
if (!module.hasSource(sourcePath)) {
|
|
198
|
-
continue;
|
|
199
|
-
}
|
|
200
|
-
const breakpoint = breakpoints.shift();
|
|
201
|
-
if (!breakpoint) {
|
|
202
|
-
this.pendingBreakpointUpdatesBySource.delete(sourcePath);
|
|
203
|
-
continue;
|
|
204
|
-
}
|
|
205
|
-
if (breakpoints.length === 0) {
|
|
206
|
-
this.pendingBreakpointUpdatesBySource.delete(sourcePath);
|
|
207
|
-
}
|
|
208
|
-
const bytecodeOffset = module.findBytecodeOffset(breakpoint.source, breakpoint.line);
|
|
209
|
-
if (bytecodeOffset === undefined) {
|
|
210
|
-
this.log(`No source-map match for breakpoint ${path.basename(breakpoint.source)}:${breakpoint.line}`);
|
|
211
|
-
this.applyPendingBreakpointUpdates(runtime, onComplete);
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
this.log(`Resolved breakpoint ${path.basename(breakpoint.source)}:${breakpoint.line} -> bytecode offset ${bytecodeOffset}`);
|
|
215
|
-
runtime.setWasmBreakpoint(module, bytecodeOffset, {
|
|
216
|
-
onSuccess: () => {
|
|
217
|
-
this.applyPendingBreakpointUpdates(runtime, onComplete);
|
|
218
|
-
},
|
|
219
|
-
onError: (error) => {
|
|
220
|
-
this.log(`Failed to set breakpoint ${path.basename(breakpoint.source)}:${breakpoint.line}: ${error.message}`);
|
|
221
|
-
this.applyPendingBreakpointUpdates(runtime, onComplete);
|
|
222
|
-
},
|
|
223
|
-
});
|
|
1037
|
+
if (this.pendingBreakpointSources.size === 0) {
|
|
1038
|
+
onComplete?.();
|
|
224
1039
|
return;
|
|
225
1040
|
}
|
|
226
|
-
|
|
1041
|
+
this.pendingBreakpointSources.clear();
|
|
1042
|
+
const wasmBytecodeOffsets = new Set();
|
|
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
|
+
}
|
|
1055
|
+
runtime.setWasmBreakpoints(wasmModule, Array.from(wasmBytecodeOffsets).toSorted((left, right) => left - right), {
|
|
1056
|
+
onSuccess: () => {
|
|
1057
|
+
this.log(`Breakpoint set installed: ${wasmBytecodeOffsets.size} locations`);
|
|
1058
|
+
onComplete?.();
|
|
1059
|
+
},
|
|
1060
|
+
onError: (error) => {
|
|
1061
|
+
this.log(`Failed to update breakpoints: ${error.message}`);
|
|
1062
|
+
onComplete?.();
|
|
1063
|
+
},
|
|
1064
|
+
});
|
|
227
1065
|
}
|
|
228
1066
|
disposeLoadedModule() {
|
|
1067
|
+
this.clearVariableContainers();
|
|
229
1068
|
this.loadedModule?.dispose();
|
|
230
1069
|
this.loadedModule = undefined;
|
|
231
1070
|
}
|