Skip to content

Commit 2a69399

Browse files
committed
web: render local variables as decoded, single values
Resolve each local to the pointer region holding its current value rather than every region it has occupied, narrow the trace drawer's local rendering to that region, and decode the resolved bytes to a typed value (replacing the raw formatAsDecimal fallback).
1 parent 6b0169b commit 2a69399

2 files changed

Lines changed: 63 additions & 11 deletions

File tree

‎packages/programs-react/src/components/TraceContext.tsx‎

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import React, {
1212
useRef,
1313
} from "react";
1414
import type { Pointer, Program } from "@ethdebug/format";
15-
import { dereference, Data } from "@ethdebug/pointers";
15+
import { dereference, decodeValue, Data } from "@ethdebug/pointers";
1616
import {
1717
type TraceStep,
1818
type CallInfo,
@@ -229,15 +229,33 @@ async function resolveVariableValue(
229229
pointer: Pointer,
230230
step: TraceStep,
231231
templates: Pointer.Templates,
232+
identifier?: string,
233+
type?: unknown,
232234
): Promise<string> {
233235
const state = traceStepToMachineState(step);
234236
const cursor = await dereference(pointer, {
235237
state,
236238
templates,
237239
});
238240
const view = await cursor.view(state);
241+
const decode = (data: Data): string =>
242+
decodeValue(data, type as Parameters<typeof decodeValue>[1]);
243+
244+
// Prefer the value region named after the variable. A memory-homed local's
245+
// pointer is a group that also carries frame-scaffolding regions, so
246+
// joining every region would surface the frame pointer alongside the value.
247+
// `regions.lookup` gives the last concrete region generated with a given
248+
// name — for a scalar that is exactly the value region. Decode it into a
249+
// readable value (uint -> decimal, address -> checksummed, …) by type.
250+
if (identifier) {
251+
const region = view.regions.lookup[identifier];
252+
if (region) {
253+
return decode(await view.read(region));
254+
}
255+
}
239256

240-
// Collect values from all regions
257+
// Fallback: no identifier-named region — read every region (previous
258+
// behavior), covering pointers whose value region isn't identifier-named.
241259
const values: Data[] = [];
242260
for (const region of view.regions) {
243261
const data = await view.read(region);
@@ -248,12 +266,12 @@ async function resolveVariableValue(
248266
return "0x";
249267
}
250268

251-
// Single region: return its hex value
269+
// Single region: decode its value
252270
if (values.length === 1) {
253-
return values[0].toHex();
271+
return decode(values[0]);
254272
}
255273

256-
// Multiple regions: concatenate hex values
274+
// Multiple regions (composite) — not a scalar; concatenate raw hex.
257275
return values.map((d) => d.toHex()).join(", ");
258276
}
259277

@@ -330,6 +348,8 @@ export function TraceProvider({
330348
v.pointer as Pointer,
331349
currentStep,
332350
templates,
351+
v.identifier,
352+
v.type,
333353
);
334354
if (!cancelled) {
335355
resolved[index] = {
@@ -427,6 +447,7 @@ export function TraceProvider({
427447
ptr as Pointer,
428448
step,
429449
templates,
450+
names?.[i],
430451
);
431452
args[i] = { ...args[i], value };
432453
} catch (err) {

‎packages/web/src/theme/ProgramExample/TraceDrawer.tsx‎

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@ import {
2424
extractSourceRange,
2525
} from "@ethdebug/bugc-react";
2626
import { Executor, createTraceCollector, type TraceStep } from "@ethdebug/evm";
27-
import { dereference, Data, type Machine } from "@ethdebug/pointers";
27+
import {
28+
dereference,
29+
decodeValue,
30+
Data,
31+
type Machine,
32+
} from "@ethdebug/pointers";
2833
import {
2934
buildCallStack,
3035
extractCallInfoFromInstruction,
@@ -329,7 +334,7 @@ function TraceDrawerContent(): JSX.Element {
329334

330335
const resolvePromises = ptrs.map(async (ptr, i) => {
331336
try {
332-
const value = await resolvePointer(ptr, state);
337+
const value = await resolvePointer(ptr, state, names?.[i]);
333338
args[i] = { ...args[i], value };
334339
} catch (err) {
335340
args[i] = {
@@ -380,7 +385,10 @@ function TraceDrawerContent(): JSX.Element {
380385
currentVariables.map(async (v) => {
381386
if (!v.pointer) return;
382387
try {
383-
next.set(v.identifier, await resolvePointer(v.pointer, state));
388+
next.set(
389+
v.identifier,
390+
await resolvePointer(v.pointer, state, v.identifier, v.rawType),
391+
);
384392
} catch {
385393
// leave unresolved
386394
}
@@ -1073,7 +1081,10 @@ function formatBigInt(value: bigint): string {
10731081
// Variable type extracted from debug context
10741082
interface Variable {
10751083
identifier: string;
1084+
/** Formatted type string, for display. */
10761085
type?: string;
1086+
/** Raw ethdebug type specifier, for value decoding. */
1087+
rawType?: unknown;
10771088
pointer?: unknown;
10781089
}
10791090

@@ -1094,7 +1105,7 @@ function VariablesDisplay({
10941105
<div key={i} className="variable-item">
10951106
<span className="variable-name">{variable.identifier}</span>
10961107
{value !== undefined && (
1097-
<code className="variable-value">{formatAsDecimal(value)}</code>
1108+
<code className="variable-value">{value}</code>
10981109
)}
10991110
{variable.type && (
11001111
<span className="variable-type">{variable.type}</span>
@@ -1203,6 +1214,7 @@ function extractVariables(context: unknown): Variable[] {
12031214
variables.push({
12041215
identifier: String(variable.identifier),
12051216
type: variable.type ? formatType(variable.type) : undefined,
1217+
rawType: variable.type,
12061218
pointer: variable.pointer,
12071219
});
12081220
}
@@ -1390,25 +1402,44 @@ function traceStepToState(
13901402
}
13911403

13921404
/**
1393-
* Resolve a single pointer against a machine state.
1405+
* Resolve a single pointer against a machine state, decoding the value
1406+
* region into a readable string when the variable's type is known.
13941407
*/
13951408
async function resolvePointer(
13961409
pointer: unknown,
13971410
state: Machine.State,
1411+
identifier?: string,
1412+
type?: unknown,
13981413
): Promise<string> {
13991414
const cursor = await dereference(
14001415
pointer as Parameters<typeof dereference>[0],
14011416
{ state, templates: {} },
14021417
);
14031418
const view = await cursor.view(state);
1419+
const decode = (data: Data): string =>
1420+
decodeValue(data, type as Parameters<typeof decodeValue>[1]);
1421+
1422+
// Prefer the value region named after the variable. A memory-homed local's
1423+
// pointer is a group that also carries frame-scaffolding regions, so
1424+
// joining every region would surface the frame pointer alongside the value.
1425+
if (identifier) {
1426+
const region = view.regions.lookup[identifier];
1427+
if (region) {
1428+
return decode(await view.read(region));
1429+
}
1430+
}
14041431

1432+
// Fallback: no identifier-named region — read every region (covers
1433+
// pointers whose value region isn't identifier-named, e.g. anonymous
1434+
// single-region storage locals).
14051435
const values: Data[] = [];
14061436
for (const region of view.regions) {
14071437
values.push(await view.read(region));
14081438
}
14091439

14101440
if (values.length === 0) return "0x";
1411-
if (values.length === 1) return values[0].toHex();
1441+
if (values.length === 1) return decode(values[0]);
1442+
// Composite (multiple regions) — not a scalar; show raw hex words.
14121443
return values.map((d) => d.toHex()).join(", ");
14131444
}
14141445

0 commit comments

Comments
 (0)