From 3e5fd7c3142260de558956d9eb713125777e2f1e Mon Sep 17 00:00:00 2001 From: sumnoon Date: Mon, 28 Sep 2026 23:55:03 +0600 Subject: [PATCH] Show a node's value next to its name in call labels A pointer argument or return value was labelled with the heap object's name only (pre_order(a3)), so telling which node a call was on meant opening Memory. Labels now add the object's first plain field, such as val or data: pre_order(a3:1). Values over 11 characters are left out. Co-Authored-By: Claude Opus 5.5 --- docs/usage.md | 4 ++++ web/src/callHistory.ts | 17 ++++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index 522d92f..ca809da 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -82,6 +82,10 @@ Each node shows the call with its argument values, such as `towerOfHanoi(2, "B", "C", "A")`. Methods drop their class name and the hidden `this` pointer, so a LeetCode `Solution::` method reads like a plain function. Nodes widen to fit the arguments (up to 32 characters); hover a node for its full label. +A pointer argument names the heap object it points to, as the Memory tab does, +followed by that object's first plain field: `pre_order(a3:1)` is a call on the node +whose `val` is 1. An unexpected number there, such as a never-set `val`, shows up +without opening Memory. Calls branch in invocation order and retain recorded return values. L/R labels mean first/second child calls, not necessarily `left`/`right` fields. The active diff --git a/web/src/callHistory.ts b/web/src/callHistory.ts index 6e715c5..c140545 100644 --- a/web/src/callHistory.ts +++ b/web/src/callHistory.ts @@ -5,13 +5,24 @@ export type CallNode = {id: string; parent: string | null; children: string[]; f first: number; last: number; state: 'active' | 'waiting' | 'completed' | 'interrupted'; label: string; returned: boolean; returnValue: string | null}; +/** A heap object by name and, when it has one, its value: `a3:1` for a node whose first + * plain field (val, data, key...) holds 1. The value is what the reader recognises; + * the name is what the Memory tab calls the same box. */ +function objectName(stop: Snapshot, address: string) { + const object = stop.heap[address]; + if (!object) return 'ptr'; + const field = object.fields?.find(item => !item.state && !item.type?.includes('*') && item.value != null); + const value = field && shortValue(field.value!).text; + return value && value.length <= 11 ? `${object.allocation_id}:${value}` : object.allocation_id; +} + /** An argument as the reader thinks of it: a pointer names the object it points - * at (a3, null) rather than an address that changes on every run. */ + * at (a3:1, null) rather than an address that changes on every run. */ function argument(local: Local, stop: Snapshot) { const pointer = ownPointer(local); if (pointer) { if (pointer.state === 'null') return 'null'; - if (pointer.state === 'heap' && pointer.target) return stop.heap[pointer.target]?.allocation_id ?? 'ptr'; + if (pointer.state === 'heap' && pointer.target) return objectName(stop, pointer.target); if (pointer.state === 'dangling') return 'dangling'; return local.value ?? '?'; } @@ -23,7 +34,7 @@ function argument(local: Local, stop: Snapshot) { function returned(value: string | null, stop: Snapshot) { if (value === null) return null; const address = value.match(/^(?:\([^)]*\)\s*)?(0x[0-9a-f]+)$/)?.[1]; - if (address) return address === '0x0' ? 'null' : stop.heap[address]?.allocation_id ?? value; + if (address) return address === '0x0' ? 'null' : stop.heap[address] ? objectName(stop, address) : value; return shortValue(value).text; }