-
Notifications
You must be signed in to change notification settings - Fork 3
ecalli traces support #476
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
tomusdrw
commented
Feb 5, 2026
- load ecalli trace file
- fixes
- more fixes
✅ Deploy Preview for pvm-debugger ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the
📝 WalkthroughWalkthroughThis pull request introduces a comprehensive host call tracing system implementing JIP-6 trace format specification. The changes add trace parsing and validation libraries, integrate trace configuration UI in debugger settings, enhance host call dialogs with trace-aware editing and restart capabilities, implement trace file loading in the program loader, refactor debugger state management to support trace data, add error boundary handling, remove deprecated serviceId propagation from workers, and include end-to-end test coverage for trace loading workflows. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant Loader as ProgramLoader
participant Parser as hostCallTrace
participant Redux as Redux Store
participant Dialog as HostCallDialog
participant Worker as Web Worker
User->>Loader: Upload trace file (.trace/.log)
Loader->>Parser: parseTrace(content)
Parser->>Parser: Parse program, start, ecalli, termination
Parser-->>Loader: ParsedTrace with entries
Loader->>Redux: dispatch(setHostCallsTrace(content))
Redux-->>Loader: State updated
Loader->>User: Display trace summary (host call count)
User->>User: Click Run/Step
Loader->>Worker: Load program + step
Worker->>Worker: Execute, hit host call
Worker-->>Redux: Debugger state update
Redux->>Dialog: selectHostCallsTrace available
Dialog->>Dialog: findHostCallEntry from trace
Dialog->>Dialog: Detect state mismatches
Dialog->>User: Display trace entry + validate state
User->>Dialog: Apply changes or Auto-continue
Dialog->>Dialog: Apply register/memory edits from trace
Dialog->>Worker: Resume with applied state
Worker->>Worker: Continue execution from trace state
Worker-->>Redux: Updated state
Redux->>Dialog: Update display
sequenceDiagram
actor User
participant Dialog as DebuggerSettings
participant Config as TraceConfigDialog
participant Editor as CodeMirror
participant Validator as hostCallTrace
participant Redux as Redux Store
User->>Dialog: Open Debugger Settings
Dialog->>Dialog: Display trace status (if loaded)
User->>Dialog: Click Configure button
Dialog->>Config: open=true
Config->>Redux: selectHostCallsTrace, selectAutoContinueOnHostCalls
Redux-->>Config: Current trace state
Config->>Editor: Load trace content + validation
User->>Config: Edit trace / Upload file
Config->>Validator: validateTrace(content)
Validator->>Validator: Parse lines, check format
Validator-->>Config: errors[] or success
alt Validation Success
Config->>Config: Display summary (host calls, termination)
else Validation Failed
Config->>Config: Show error messages
end
User->>Config: Toggle Auto-continue
Config->>Redux: dispatch(setAutoContinueOnHostCalls(bool))
User->>Config: Click Apply
Config->>Redux: dispatch(setHostCallsTrace(content))
Redux-->>Config: State persisted
Config->>Dialog: onOpenChange(false)
Dialog->>Dialog: Update trace status display
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 20
🤖 Fix all issues with AI agents
In `@src/components/HostCallDialog/handlers/HostCallActionButtons.tsx`:
- Around line 15-20: The restart button in HostCallActionButtons lacks an
explicit type, which can cause unintended form submission when this component is
used inside a form; update the <button> element that calls onRestart (the
"restart" control) to include type="button" so it does not act as a submit
button, keeping the existing className, onClick handler, and disabled prop
unchanged.
In `@src/components/HostCallDialog/index.tsx`:
- Around line 106-108: currentState may be undefined so accessing
currentState.regs and currentState.gas can throw; update the expressions that
compute finalRegs and currentGas/finalGas (the lines assigning finalRegs,
currentGas, finalGas in HostCallDialog/index.tsx) to use optional chaining or
nullish coalescing when reading currentState (e.g., currentState?.regs and
currentState?.gas) so you fall back to DEFAULT_REGS and DEFAULT_GAS safely, and
keep the pendingRegs/pendingGas precedence logic intact.
In `@src/components/ProgramLoader/Links.tsx`:
- Around line 68-81: The new external links in ProgramLoader's Links.tsx (the
<a> elements that use target="_blank" around the ExternalLink icon and the inner
JIP-6 text) are missing rel attributes; update both anchor tags to include
rel="noopener noreferrer" to prevent reverse-tabnabbing and ensure safe external
navigation. Locate the anchors in the Links.tsx component (the <a
href="https://...JIP-6.md" target="_blank"> wrapping ExternalLink and the inner
<a className="text-brand-dark..." href="https://...JIP-6.md">) and add
rel="noopener noreferrer" to each.
In `@src/components/ProgramLoader/Loader.tsx`:
- Around line 64-74: The handleFileUpload callback currently calls parseTrace
and getTraceSummary without protection; wrap the parseTrace/getTraceSummary
calls in a try-catch inside handleFileUpload so malformed trace data doesn't
crash the component — on success keep the existing setTraceContent(trace) and
setTraceSummary(getTraceSummary(...)), and on error call setTraceContent(null)
and setTraceSummary(null) (and optionally log the error or surface a
user-friendly message) so the component remains stable; reference
handleFileUpload, parseTrace, getTraceSummary, setTraceContent, setTraceSummary,
and setProgramLoad when applying the change.
In `@src/components/ProgramLoader/loadingUtils.test.ts`:
- Line 5: Rename the test file from loadingUtils.test.ts to
loading-utils.test.ts and update all references/imports to that file throughout
the repo (e.g., any import statements, test index files, or CI/test-glob
patterns that explicitly reference the old name); ensure the test suite name
("describe('loadingUtils', ...)") can remain unchanged or be adjusted for
clarity but is not required to match the filename.
In `@src/components/ProgramLoader/loadingUtils.ts`:
- Around line 48-66: The buildSpiArgsFromMemoryWrites function can allocate a
huge Uint8Array for sparse far-apart addresses; add a hard cap (e.g. const
MAX_SPI_ARG_SIZE = 10 * 1024 * 1024) and check totalSize after computing
lastAddr - firstAddr, and if totalSize > MAX_SPI_ARG_SIZE throw a clear
RangeError/ValidationError (include totalSize and MAX in the message) to fail
fast instead of allocating; apply the same guard to the other similar routine
handling memoryWrites (the other block around the 83-90 logic) and use the same
MAX constant and error handling so both places consistently refuse oversized
spans.
- Around line 68-73: The function tryParseTraceFile uses parseTrace but doesn't
check for parse errors before reading prelude fields; update tryParseTraceFile
to detect parse failures from parseTrace (e.g., an errors or success flag on the
returned trace) and short‑circuit (return null or a LoadFileResult error) when
parseTrace indicates errors so you never access trace.prelude.program with an
invalid parse; locate tryParseTraceFile and parseTrace usages and add a guard
that rejects traces with parse errors before any further processing of
ExpectedState or building the LoadFileResult.
- Around line 134-142: The current mapping uses memory.map to create one
PageMapItem per chunk which misses pages when a chunk spans multiple 4KiB pages;
change the construction of pageMap so that for each chunk (from the memory
array) you compute the firstPage = Math.floor(chunk.address / 4096) and lastPage
= Math.floor((chunk.address + (chunk.length ?? 0) - 1) / 4096) and push a
PageMapItem for every page index in that range with address = (pageIndex << 12)
(or pageIndex*4096) & ~0xfff, length = 4096 and "is-writable" = true; then keep
the existing dedupe into uniquePageMap (filter by address) to remove duplicates.
In `@src/hooks/useDebuggerActions.ts`:
- Around line 81-83: The code reads workers[0]?.memoryRanges into memoryRanges
and calls syncMemoryRangeAllWorkers which will throw if undefined; change to
default to an empty array and only call syncMemoryRangeAllWorkers when the array
is non-empty (e.g., const memoryRanges = workers[0]?.memoryRanges ?? []; if
(memoryRanges.length) syncMemoryRangeAllWorkers(...)). Apply the same
guard/default pattern wherever memoryRanges is read and passed to
syncMemoryRangeAllWorkers in this module (the other occurrences near the other
syncMemoryRangeAllWorkers calls).
- Around line 84-90: The current loop calls destroyWorker for each WorkerState
which broadcasts Commands.UNLOAD to all workers repeatedly; change this by
adding a single "destroyAllWorkers" flow or modify destroyWorker so it targets
only the specific worker id: update destroyWorker (or add destroyAllWorkers) to
accept a flag or separate path that sends Commands.UNLOAD only to the intended
worker socket instead of broadcasting to all, then replace the
Promise.all(workers.map(...dispatch(destroyWorker...))) calls with a single call
to the new destroy-all helper or call destroyWorker per-id in its non-broadcast
mode; ensure recreateWorker usage remains unchanged and keep references to
WorkerState, recreateWorker, destroyWorker and Commands.UNLOAD when making the
change.
In `@src/lib/hostCallTrace.test.ts`:
- Line 6: The test file is named in camelCase; rename the file from
src/lib/hostCallTrace.test.ts to kebab-case host-call-trace.test.ts and update
any imports or references (e.g., test runner configs or other files that import
this test) to point to host-call-trace.test.ts; also update any describe/title
strings if you want consistency (the test's describe("hostCallTrace", ...) can
remain or be changed to "host-call-trace" if desired) and run the test suite to
ensure no broken references remain.
In `@src/lib/hostCallTrace.ts`:
- Around line 1-9: The file name hostCallTrace.ts uses camelCase but the repo
requires kebab-case; rename the file to host-call-trace.ts and update all
imports referencing hostCallTrace (e.g., any import statements that import the
module providing "ecalli / PVM IO Trace Parser and Types" or types from that
module) to use the new kebab-case name; ensure build and tests are updated to
reference host-call-trace.ts and run a full repo search/replace for
"hostCallTrace" to catch any remaining references.
- Around line 205-235: The parseMemreadLine and parseMemwriteLine functions
currently ignore mismatches between the declared len and the actual decoded byte
count; update both functions to call parseHexBytes(dataHex), verify that the
returned byte array length equals parseInt(lenStr, 10), and throw a descriptive
Error (including the parsed address, declared len, and actual data length) if
they differ so callers cannot silently proceed with inconsistent memory sizes;
keep the existing parseAddress/parseHexBytes usage and ensure the thrown error
is clear for debugging.
- Around line 618-636: findHostCallEntry currently picks the first host call
with a matching pc (from trace.hostCalls.slice(indexInTrace)) which can return a
false mismatch when a later entry has the correct hostCallIndex and acceptable
gas; change the selection logic so after building matchingEntries (pc match and
hc.gas <= gas) you first try to find an entry whose hostCallIndex equals the
provided hostCallIndex and gas <= gas (e.g. matchingEntries.find(hc =>
hc.hostCallIndex === hostCallIndex && hc.gas <= gas)), use that as entry if
found, otherwise fall back to matchingEntries[0]; keep the rest of the
mismatches handling unchanged but reference the chosen entry variable (entry) as
before.
- Around line 337-468: parseTrace currently consumes raw trace text without Zod
validation; add a Zod schema for the overall ParsedTrace shape (including
prelude.program, prelude.start, initialMemoryWrites, hostCalls entries with
memoryReads/writes/registerWrites/gasAfter, termination, and errors) and
validate the input structure before doing line-by-line parsing. Implement the
schema near the top of this module, call schema.safeParse or parse inside
parseTrace at the start (or validate a preliminary parsed object after minimal
parsing) and on validation failure return or populate result.errors with the zod
error messages (or throw a clear error) so downstream code gets a typed,
validated ParsedTrace; reference parseTrace, ParsedTrace, and the
result.hostCalls/currentHostCall shapes when adding the schema and handling
failures.
In `@src/store/debugger/debuggerSlice.ts`:
- Around line 225-237: The reducer setHostCallsTrace currently clears
state.hostCallsTrace when action.payload is null/empty but leaves
state.nextHostCallIndex and state.pendingHostCall unchanged, causing stale UI
state; update setHostCallsTrace to also reset state.nextHostCallIndex to 0 and
state.pendingHostCall to null whenever action.payload is null or an
empty/whitespace string so that hostCallsTrace, nextHostCallIndex, and
pendingHostCall are consistent.
- Around line 225-233: The reducer setHostCallsTrace currently stores parsed
traces without validating them; call the existing validateTrace(action.payload)
(or run the payload through the shared Zod schema) before calling parseTrace,
and if validation reports errors throw an Error (or reject the payload) so
invalid trace content is not persisted; only after validateTrace succeeds
proceed to parseTrace and set state.hostCallsTrace with rawContent and parsed.
Ensure this change applies to the setHostCallsTrace reducer so Loader.tsx
dispatches cannot write invalid traces, keeping TraceConfigDialog.tsx validation
redundant but harmless.
In `@src/store/workers/workersSlice.ts`:
- Around line 296-342: In the auto-continue path you need to advance each
worker's host-call index and actually await resuming execution instead of
fire-and-forget; after computing newRegs/newGas/memoryEdits and after
successfully applying SET_STATE/SET_MEMORY via asyncWorkerPostMessage, increment
the host-call index on the state you pass to
dispatch(setWorkerCurrentState(...)) (or dispatch a dedicated advance-host-call
action) so the worker's currentState reflects the progressed host-call, and then
await runAllWorkers() (use await dispatch(runAllWorkers()) if it's a thunk)
rather than calling it without awaiting, so trace matching and resume errors are
not lost.
In `@src/utils/loggerService.tsx`:
- Around line 19-20: The startup log uses a template literal with embedded
newline/indentation and awkward phrasing; replace the template literal in the
console.info call in src/utils/loggerService.tsx with a plain single-line string
and fix grammar (e.g., "Logger initialized — watch console for logs."), removing
the extra whitespace/newline so the message is concise and properly punctuated.
In `@tests/trace-loading.spec.ts`:
- Around line 13-16: The test uses a hard-coded tracePath and directly calls
fileInput.setInputFiles, which bypasses shared Playwright fixtures; replace the
direct path usage with the shared fixture from tests/utils (the fixture that
supplies the deterministic file or FilePayload) and pass that fixture into the
test instead of constructing tracePath, then use that fixture value with the
existing fileInput locator and setInputFiles call (update the test signature to
accept the fixture and substitute tracePath in the setInputFiles invocation);
apply the same change to the other occurrence around lines 45-48.
🧹 Nitpick comments (12)
tests/trace-loading.spec.ts (1)
18-27: Replace fixed sleeps with condition-based waits.
waitForTimeoutadds flakiness; theexpect(...).toBeVisible/Enabledcalls already include waits. At lines 18–27, remove the 2000ms sleep before checking button visibility. At lines 49–65, remove both the 2000ms and 3000ms sleeps, and replace the 10000ms sleep after Run with a condition-based wait for either the host-call dialog or program-status visibility.Additionally, reuse the
openDebuggerfixture fromtests/utils/actions.tsinstead of duplicating thepage.gotoandlocalStorage.clearcalls.♻️ Suggested cleanup
- await page.waitForTimeout(2000); ... - await page.waitForTimeout(3000); ... - await page.waitForTimeout(10000);Replace the 10000ms sleep with:
await Promise.race([ page.locator('[role="dialog"]').waitFor({ state: 'visible' }), expect(page.getByTestId("program-status")).toBeVisible() ]).catch(() => {});src/lib/hostCallTrace.test.ts (2)
1-4: Avoid__dirnameif Vitest runs in ESM mode.If tests execute as ESM,
__dirnamecan be undefined. Consider usingimport.meta.url+fileURLToPathinstead.🔧 Safer ESM path resolution
-import * as path from "path"; +import { fileURLToPath } from "url"; @@ - const tracePath = path.join(__dirname, "../../io-trace-output.log"); + const tracePath = fileURLToPath(new URL("../../io-trace-output.log", import.meta.url));Also applies to: 67-68
69-72: Use a committed fixture to keep this test deterministic.Right now the test silently skips when the file is missing. Consider adding a small fixture trace under version control so the test always exercises the parsing path.
src/components/ProgramLoader/loadingUtils.test.ts (2)
1-3: Avoid__dirnameif Vitest runs in ESM mode.If tests execute as ESM,
__dirnamecan be undefined. Preferimport.meta.url+fileURLToPath.🔧 Safer ESM path resolution
-import * as path from "path"; +import { fileURLToPath } from "url"; @@ - const tracePath = path.join(__dirname, "../../../io-trace-output.log"); + const tracePath = fileURLToPath(new URL("../../../io-trace-output.log", import.meta.url));Also applies to: 8-9
10-13: Use a committed fixture to keep this test deterministic.Right now the test silently skips when the file is missing. Consider checking in a small fixture trace instead so the test always exercises the parsing path.
src/components/ErrorBoundary.tsx (2)
60-65: Unnecessary state reset before page reload.The
setStatecall on line 62 has no effect sincewindow.location.reload()immediately follows on line 64, discarding any in-memory state changes.🔧 Suggested simplification
<button onClick={() => { - this.setState({ hasError: false, error: null, errorInfo: null }); window.location.hash = "#/load"; window.location.reload(); }}
24-28: Consider using the existing logger service for consistency.The codebase has a
logger.errorservice (seesrc/utils/loggerService.tsx) that provides centralized error handling. Using it here would maintain consistency across the application.src/components/ProgramLoader/Loader.tsx (1)
195-195: Misleading variable name.
hasTraceWithEntrypointsimply checks iftraceContent !== null, but the name suggests it verifies that the trace contains entrypoint information. Consider renaming tohasTracefor clarity.✏️ Suggested rename
- const hasTraceWithEntrypoint = traceContent !== null; + const hasTrace = traceContent !== null;Then update usages on lines 198 and 311.
src/components/DebuggerSettings/TraceConfigDialog.tsx (2)
176-192: Consider using dropzone'sopen()method instead of creating a manual input element.The dropzone hook provides an
open()function that can trigger the file picker. This would be more consistent and avoid duplicating the file accept logic.♻️ Suggested refactor using dropzone open()
- const { getRootProps, getInputProps, isDragActive } = useDropzone({ + const { getRootProps, getInputProps, isDragActive, open } = useDropzone({ onDrop: handleFileDrop, accept: { "text/plain": [".txt", ".trace", ".log"] }, noClick: true, multiple: false, });Then update the button:
<Button variant="outline" size="sm" - onClick={() => { - const input = document.createElement("input"); - input.type = "file"; - input.accept = ".txt,.trace,.log"; - input.onchange = (e) => { - const file = (e.target as HTMLInputElement).files?.[0]; - if (file) handleFileDrop([file]); - }; - input.click(); - }} + onClick={open} >
128-129: Unsafe type assertion on FileReader result.The
e.target?.resultis cast tostringwithout verification. WhilereadAsTextshould return a string, defensive coding would check the type.🛡️ Suggested defensive check
reader.onload = (e) => { - const content = e.target?.result as string; + const content = e.target?.result; + if (typeof content !== "string") return; const { stripped, programLine: pl } = stripProgramLine(content);src/components/HostCallDialog/MemoryEditor.tsx (1)
130-130: Potential unnecessary memory reloads whenpendingWritesreference changes.Adding
pendingWritesto the dependency array causesloadMemoryto re-run whenever the array reference changes, even if the content is identical. For large memory regions, this could cause noticeable re-fetching.Consider memoizing
pendingWritesin the parent component or using a stable reference comparison if this becomes a performance concern.src/components/HostCallDialog/DefaultHostCallContent.tsx (1)
36-53: Cap log memory reads to avoid oversized allocations.
Lengths come from registers; a defensive maximum prevents large reads or UI stalls if the target/message lengths are unexpectedly huge.Suggested guard
const decoder = new TextDecoder("utf8"); +const MAX_LOG_BYTES = 16_384; ... - const targetLength = Number(currentRegs[9] ?? 0n); + const targetLength = Math.max(0, Math.min(Number(currentRegs[9] ?? 0n), MAX_LOG_BYTES)); ... - const msgLength = Number(currentRegs[11] ?? 0n); + const msgLength = Math.max(0, Math.min(Number(currentRegs[11] ?? 0n), MAX_LOG_BYTES));Also applies to: 111-154, 251-269
| <button | ||
| className="text-sm text-muted-foreground hover:text-foreground hover:underline disabled:opacity-50" | ||
| onClick={onRestart} | ||
| disabled={disabled} | ||
| > | ||
| restart |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Set type="button" on the restart control.
Prevents unintended form submission if this component is rendered inside a form.
🔧 Proposed fix
- <button
+ <button type="button"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <button | |
| className="text-sm text-muted-foreground hover:text-foreground hover:underline disabled:opacity-50" | |
| onClick={onRestart} | |
| disabled={disabled} | |
| > | |
| restart | |
| <button type="button" | |
| className="text-sm text-muted-foreground hover:text-foreground hover:underline disabled:opacity-50" | |
| onClick={onRestart} | |
| disabled={disabled} | |
| > | |
| restart |
🤖 Prompt for AI Agents
In `@src/components/HostCallDialog/handlers/HostCallActionButtons.tsx` around
lines 15 - 20, The restart button in HostCallActionButtons lacks an explicit
type, which can cause unintended form submission when this component is used
inside a form; update the <button> element that calls onRestart (the "restart"
control) to include type="button" so it does not act as a submit button, keeping
the existing className, onClick handler, and disabled prop unchanged.
| const finalRegs = pendingRegs ?? currentState.regs ?? DEFAULT_REGS; | ||
| const currentGas = currentState.gas ?? DEFAULT_GAS; | ||
| const finalGas = gas ?? pendingGas ?? (currentGas > 10n ? currentGas - 10n : 0n); | ||
|
|
||
| // Combine memory edits from host calls with pending memory from default UI | ||
| const finalMemoryEdits: MemoryEdit[] = []; | ||
| if (memoryEdits && memoryEdits.length > 0) { | ||
| finalMemoryEdits.push(...memoryEdits); | ||
| } | ||
| if (pendingMemoryEdits.length > 0) { | ||
| finalMemoryEdits.push(...pendingMemoryEdits); | ||
| } | ||
| const finalGas = pendingGas ?? (currentGas > 10n ? currentGas - 10n : 0n); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential null reference when accessing currentState properties.
currentState is typed as ExpectedState | undefined (line 60), but lines 106-108 access currentState.regs and currentState.gas without null checks, which would throw if currentState is undefined.
🛡️ Proposed fix with optional chaining
// Use provided values or fall back to pending/current state
- const finalRegs = pendingRegs ?? currentState.regs ?? DEFAULT_REGS;
- const currentGas = currentState.gas ?? DEFAULT_GAS;
+ const finalRegs = pendingRegs ?? currentState?.regs ?? DEFAULT_REGS;
+ const currentGas = currentState?.gas ?? DEFAULT_GAS;
const finalGas = pendingGas ?? (currentGas > 10n ? currentGas - 10n : 0n);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const finalRegs = pendingRegs ?? currentState.regs ?? DEFAULT_REGS; | |
| const currentGas = currentState.gas ?? DEFAULT_GAS; | |
| const finalGas = gas ?? pendingGas ?? (currentGas > 10n ? currentGas - 10n : 0n); | |
| // Combine memory edits from host calls with pending memory from default UI | |
| const finalMemoryEdits: MemoryEdit[] = []; | |
| if (memoryEdits && memoryEdits.length > 0) { | |
| finalMemoryEdits.push(...memoryEdits); | |
| } | |
| if (pendingMemoryEdits.length > 0) { | |
| finalMemoryEdits.push(...pendingMemoryEdits); | |
| } | |
| const finalGas = pendingGas ?? (currentGas > 10n ? currentGas - 10n : 0n); | |
| const finalRegs = pendingRegs ?? currentState?.regs ?? DEFAULT_REGS; | |
| const currentGas = currentState?.gas ?? DEFAULT_GAS; | |
| const finalGas = pendingGas ?? (currentGas > 10n ? currentGas - 10n : 0n); |
🤖 Prompt for AI Agents
In `@src/components/HostCallDialog/index.tsx` around lines 106 - 108, currentState
may be undefined so accessing currentState.regs and currentState.gas can throw;
update the expressions that compute finalRegs and currentGas/finalGas (the lines
assigning finalRegs, currentGas, finalGas in HostCallDialog/index.tsx) to use
optional chaining or nullish coalescing when reading currentState (e.g.,
currentState?.regs and currentState?.gas) so you fall back to DEFAULT_REGS and
DEFAULT_GAS safely, and keep the pendingRegs/pendingGas precedence logic intact.
| const handleFileUpload = useCallback((output: ProgramUploadFileOutput, trace?: string) => { | ||
| setProgramLoad(output); | ||
| if (trace) { | ||
| setTraceContent(trace); | ||
| const parsed = parseTrace(trace); | ||
| setTraceSummary(getTraceSummary(parsed)); | ||
| } else { | ||
| setTraceContent(null); | ||
| setTraceSummary(null); | ||
| } | ||
| }, []); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing error handling for trace parsing.
If parseTrace or getTraceSummary throws an exception (e.g., malformed trace content), it will crash the component. Consider wrapping in try-catch.
🛡️ Proposed fix with error handling
const handleFileUpload = useCallback((output: ProgramUploadFileOutput, trace?: string) => {
setProgramLoad(output);
if (trace) {
- setTraceContent(trace);
- const parsed = parseTrace(trace);
- setTraceSummary(getTraceSummary(parsed));
+ try {
+ setTraceContent(trace);
+ const parsed = parseTrace(trace);
+ setTraceSummary(getTraceSummary(parsed));
+ } catch (e) {
+ console.error("Failed to parse trace:", e);
+ setTraceContent(null);
+ setTraceSummary(null);
+ }
} else {
setTraceContent(null);
setTraceSummary(null);
}
}, []);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const handleFileUpload = useCallback((output: ProgramUploadFileOutput, trace?: string) => { | |
| setProgramLoad(output); | |
| if (trace) { | |
| setTraceContent(trace); | |
| const parsed = parseTrace(trace); | |
| setTraceSummary(getTraceSummary(parsed)); | |
| } else { | |
| setTraceContent(null); | |
| setTraceSummary(null); | |
| } | |
| }, []); | |
| const handleFileUpload = useCallback((output: ProgramUploadFileOutput, trace?: string) => { | |
| setProgramLoad(output); | |
| if (trace) { | |
| try { | |
| setTraceContent(trace); | |
| const parsed = parseTrace(trace); | |
| setTraceSummary(getTraceSummary(parsed)); | |
| } catch (e) { | |
| console.error("Failed to parse trace:", e); | |
| setTraceContent(null); | |
| setTraceSummary(null); | |
| } | |
| } else { | |
| setTraceContent(null); | |
| setTraceSummary(null); | |
| } | |
| }, []); |
🤖 Prompt for AI Agents
In `@src/components/ProgramLoader/Loader.tsx` around lines 64 - 74, The
handleFileUpload callback currently calls parseTrace and getTraceSummary without
protection; wrap the parseTrace/getTraceSummary calls in a try-catch inside
handleFileUpload so malformed trace data doesn't crash the component — on
success keep the existing setTraceContent(trace) and
setTraceSummary(getTraceSummary(...)), and on error call setTraceContent(null)
and setTraceSummary(null) (and optionally log the error or surface a
user-friendly message) so the component remains stable; reference
handleFileUpload, parseTrace, getTraceSummary, setTraceContent, setTraceSummary,
and setProgramLoad when applying the change.
| import * as fs from "fs"; | ||
| import * as path from "path"; | ||
|
|
||
| describe("loadingUtils", () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Rename this test file to kebab-case.
Please rename to loading-utils.test.ts to align with repo naming rules and update any references.
As per coding guidelines: Use descriptive filenames in kebab-case (e.g., memory-range-panel.tsx).
🤖 Prompt for AI Agents
In `@src/components/ProgramLoader/loadingUtils.test.ts` at line 5, Rename the test
file from loadingUtils.test.ts to loading-utils.test.ts and update all
references/imports to that file throughout the repo (e.g., any import
statements, test index files, or CI/test-glob patterns that explicitly reference
the old name); ensure the test suite name ("describe('loadingUtils', ...)") can
remain unchanged or be adjusted for clarity but is not required to match the
filename.
| setHostCallsTrace(state, action: { payload: string | null }) { | ||
| if (action.payload === null || action.payload.trim() === "") { | ||
| state.hostCallsTrace = null; | ||
| } else { | ||
| const parsed = parseTrace(action.payload); | ||
| state.hostCallsTrace = { | ||
| rawContent: action.payload, | ||
| parsed, | ||
| }; | ||
| // reset host call index | ||
| state.nextHostCallIndex = 0; | ||
| state.pendingHostCall = null; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reset host-call progress when the trace is cleared.
When payload is null/empty, pendingHostCall and nextHostCallIndex keep their previous values, which can leave the UI in a stale host-call state.
Suggested fix
if (action.payload === null || action.payload.trim() === "") {
state.hostCallsTrace = null;
+ state.nextHostCallIndex = 0;
+ state.pendingHostCall = null;
} else {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| setHostCallsTrace(state, action: { payload: string | null }) { | |
| if (action.payload === null || action.payload.trim() === "") { | |
| state.hostCallsTrace = null; | |
| } else { | |
| const parsed = parseTrace(action.payload); | |
| state.hostCallsTrace = { | |
| rawContent: action.payload, | |
| parsed, | |
| }; | |
| // reset host call index | |
| state.nextHostCallIndex = 0; | |
| state.pendingHostCall = null; | |
| } | |
| setHostCallsTrace(state, action: { payload: string | null }) { | |
| if (action.payload === null || action.payload.trim() === "") { | |
| state.hostCallsTrace = null; | |
| state.nextHostCallIndex = 0; | |
| state.pendingHostCall = null; | |
| } else { | |
| const parsed = parseTrace(action.payload); | |
| state.hostCallsTrace = { | |
| rawContent: action.payload, | |
| parsed, | |
| }; | |
| // reset host call index | |
| state.nextHostCallIndex = 0; | |
| state.pendingHostCall = null; | |
| } |
🤖 Prompt for AI Agents
In `@src/store/debugger/debuggerSlice.ts` around lines 225 - 237, The reducer
setHostCallsTrace currently clears state.hostCallsTrace when action.payload is
null/empty but leaves state.nextHostCallIndex and state.pendingHostCall
unchanged, causing stale UI state; update setHostCallsTrace to also reset
state.nextHostCallIndex to 0 and state.pendingHostCall to null whenever
action.payload is null or an empty/whitespace string so that hostCallsTrace,
nextHostCallIndex, and pendingHostCall are consistent.
src/store/workers/workersSlice.ts
Outdated
| logger.debug(" [handleHostCall] AUTO-CONTINUING with trace data"); | ||
| // TODO [ToDr] This should be a separate method? | ||
| const newRegs = [...regs]; | ||
| for (const rw of result.entry.registerWrites) { | ||
| newRegs[rw.index] = rw.value; | ||
| } | ||
| const newGas = result.entry.gasAfter ?? gas; | ||
| const memoryEdits = result.entry.memoryWrites.map((mw) => ({ | ||
| address: mw.address, | ||
| data: mw.data, | ||
| })); | ||
|
|
||
| logger.debug(` [handleHostCall] Setting state: gas=${newGas}, memEdits=${memoryEdits.length}`); | ||
| await Promise.all( | ||
| state.workers | ||
| .filter(({ id }) => { | ||
| // Allow to call it for a single worker | ||
| return workerId ? workerId === id : true; | ||
| }) | ||
| .map(async (worker) => { | ||
| const resp = await asyncWorkerPostMessage(worker.id, worker.worker, { | ||
| command: Commands.HOST_CALL, | ||
| payload: { hostCallIdentifier: worker.exitArg ?? -1 }, | ||
| command: Commands.SET_STATE, | ||
| payload: { regs: newRegs, gas: newGas }, | ||
| }); | ||
|
|
||
| // TODO [ToDr] Handle host call response? | ||
|
|
||
| if ((getState() as RootState).debugger.isRunMode) { | ||
| dispatch(continueAllWorkers()); | ||
| } | ||
|
|
||
| if (hasCommandStatusError(resp)) { | ||
| throw new Error(resp.error.message); | ||
| } | ||
|
|
||
| logger.info(` [handleHostCall] dispatching setWorkerCurrentState for ${worker.id}`); | ||
| dispatch(setWorkerCurrentState({ id: worker.id, currentState: resp.payload.state })); | ||
|
|
||
| for (const mem of memoryEdits) { | ||
| const memResp = await asyncWorkerPostMessage(worker.id, worker.worker, { | ||
| command: Commands.SET_MEMORY, | ||
| payload: { address: mem.address, data: mem.data }, | ||
| }); | ||
|
|
||
| if (hasCommandStatusError(memResp)) { | ||
| throw new Error(memResp.error.message); | ||
| } | ||
| } | ||
| }), | ||
| ); | ||
|
|
||
| if (selectShouldContinueRunning(state)) { | ||
| dispatch(continueAllWorkers()); | ||
| } | ||
|
|
||
| logger.info(" [handleHostCall] Done - returning without opening dialog"); | ||
| dispatch(runAllWorkers()); | ||
| return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Advance host-call index and await resume in auto-continue path.
Right now the host-call index isn’t incremented and runAllWorkers() is fire-and-forget, so trace matching can repeat and resume failures are swallowed.
Suggested fix
- logger.info(" [handleHostCall] Done - returning without opening dialog");
- dispatch(runAllWorkers());
+ logger.info(" [handleHostCall] Done - advancing host-call index and resuming");
+ dispatch(
+ setPendingHostCall({
+ pendingHostCall: null,
+ nextHostCallIndex: nextHostCallIndex + 1,
+ }),
+ );
+ await dispatch(runAllWorkers()).unwrap();
return;🤖 Prompt for AI Agents
In `@src/store/workers/workersSlice.ts` around lines 296 - 342, In the
auto-continue path you need to advance each worker's host-call index and
actually await resuming execution instead of fire-and-forget; after computing
newRegs/newGas/memoryEdits and after successfully applying SET_STATE/SET_MEMORY
via asyncWorkerPostMessage, increment the host-call index on the state you pass
to dispatch(setWorkerCurrentState(...)) (or dispatch a dedicated
advance-host-call action) so the worker's currentState reflects the progressed
host-call, and then await runAllWorkers() (use await dispatch(runAllWorkers())
if it's a thunk) rather than calling it without awaiting, so trace matching and
resume errors are not lost.
src/utils/loggerService.tsx
Outdated
| console.info(`Logger initialized watch console for logs | ||
| `); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tighten the startup log string to avoid extra whitespace and fix grammar.
The template literal inserts a newline/indentation, and the message reads awkwardly. Use a plain string.
Proposed fix
- console.info(`Logger initialized watch console for logs
- `);
+ console.info("Logger initialized; watch console for logs.");📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| console.info(`Logger initialized watch console for logs | |
| `); | |
| console.info("Logger initialized; watch console for logs."); |
🤖 Prompt for AI Agents
In `@src/utils/loggerService.tsx` around lines 19 - 20, The startup log uses a
template literal with embedded newline/indentation and awkward phrasing; replace
the template literal in the console.info call in src/utils/loggerService.tsx
with a plain single-line string and fix grammar (e.g., "Logger initialized —
watch console for logs."), removing the extra whitespace/newline so the message
is concise and properly punctuated.
Fixes per CodeRabbit review: - Rename hostCallTrace files to kebab-case - Rename loadingUtils files to kebab-case - Add type="button" to restart button - Add optional chaining for currentState access - Add rel="noopener noreferrer" to external links - Add try-catch for trace parsing - Add SPI args size guard (16MB limit) - Reject traces with parse errors - Fix pageMap to handle multi-page memory chunks - Guard against undefined memoryRanges - Fix destroyWorker broadcast issue - Validate memread/memwrite length - Improve findHostCallEntry matching logic - Reset state when clearing trace - Validate trace before parsing - Await auto-continue execution - Fix logger startup message - Replace fixed waits in tests with condition-based waits
- Fix destroyWorker to always return ID for cleanup - Add retry loop to initAllWorkers for race condition robustness - Disable UI (Loader, Examples, DebuggerControls) when PVM is loading - Improve selectPVM test helper to avoid unnecessary toggling - Reload page in openDebugger to ensure clean state
|
@coderabbitai please review whether your previous comments are addressed and resolve if that's the case. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain
🏁 Script executed: Length of output: 21839 Let me check for unresolved conversations more thoroughly: 🏁 Script executed: Length of output: 91091
🟠 Major Issues (10 unresolved)
🟡 Minor Issues (8 unresolved)
✅ Outdated (2 comments - code changed)
Would you like me to help address any of these issues, or would you prefer to tackle them yourself first before I resolve the comments? |
Major fixes: - Links.tsx: Add rel="noopener noreferrer" to external links for security - host-call-trace.ts: Add Zod validation for trace structure - debuggerSlice.ts: Add input validation in setHostCallsTrace reducer - workersSlice.ts: Fix auto-continue to advance host-call index and await resume - useDebuggerActions.ts: Add guard for undefined memoryRanges - findHostCallEntry: Prefer exact host call index matches with early return Minor fixes: - trace-loading.spec.ts: Use shared Playwright fixtures - Create tests/utils/fixtures.ts for shared test utilities