warpo 3.1.0 → 3.1.2

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