Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
66a22f7
Draft function call context schemas
gnidan May 29, 2025
fc1503e
Organize schema a bit
gnidan May 31, 2025
89cb2b3
Disable unevaluated properties
gnidan May 31, 2025
241fc7f
Allow additionalProperties at top-level
gnidan May 31, 2025
16141b9
Add function identity fields to invoke/return/revert contexts
gnidan Mar 4, 2026
5685724
Fix Playground crash on page refresh in dev server
gnidan Mar 4, 2026
fb11c3e
Nest invoke/return/revert under function/ category
gnidan Mar 4, 2026
ebeff79
Rename invoke discriminant fields: internal->jump, call->message
gnidan Mar 4, 2026
50d9c30
Rewrite function context descriptions
gnidan Mar 4, 2026
565e116
Rewrite function context examples with realistic scenarios
gnidan Mar 4, 2026
ddee0bc
Add type specifier schema and use it across the format
gnidan Mar 4, 2026
addf9d5
Format
gnidan Mar 4, 2026
a2e3dde
format: add TypeScript types for function call contexts (#186)
gnidan Mar 11, 2026
c50c8d9
bugc: emit invoke/return contexts for internal function calls (#185)
gnidan Mar 11, 2026
940e0cc
bugc: add source maps for call setup instructions (#188)
gnidan Mar 11, 2026
7802c8b
Add function call tracing documentation (#187)
gnidan Mar 11, 2026
c8a793a
docs: move 4th BUG example into tracing-examples.ts
gnidan Apr 1, 2026
058aa8c
docs: add invoke.mdx prose intro and cross-link spec pages (#192)
gnidan Apr 1, 2026
898ea32
bugc: use format type guards in call context tests (#194)
gnidan Apr 1, 2026
a2cfb35
bugc: add declaration source ranges and param names to invoke/return …
gnidan Apr 1, 2026
67edefe
bugc: add debug contexts to all unmapped bytecodes (#190)
gnidan Mar 11, 2026
83005d6
Add call stack breadcrumb and call info panel (#189)
gnidan Mar 11, 2026
e98969f
docs: expand return and revert spec pages (#193)
gnidan Apr 1, 2026
38ea8d9
bugc-react: surface invoke/return/revert contexts in BytecodeView (#195)
gnidan Apr 1, 2026
9c5d1a0
bugc: map return epilogue instructions to source location (#197)
gnidan Apr 1, 2026
ff8ebcb
ui: named arguments, click-to-source, and call stack params (#198)
gnidan Apr 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions packages/bugc-react/src/components/BytecodeView.css
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,82 @@
.opcode-line .immediates {
color: var(--bugc-syntax-number);
}

/* Context badges for invoke/return/revert */
.context-badge {
cursor: pointer;
padding: 0.0625rem 0.25rem;
border-radius: 3px;
font-size: 0.75rem;
font-weight: 600;
user-select: none;
display: inline-block;
min-width: 1.2rem;
text-align: center;
transition: all 0.15s ease;
}

.context-badge-invoke {
color: var(--bugc-accent-blue);
background-color: var(--bugc-accent-blue-bg);
}

.context-badge-invoke:hover {
background-color: var(--bugc-accent-blue);
color: var(--bugc-bg-primary);
}

.context-badge-return {
color: var(--bugc-accent-green);
background-color: var(--bugc-accent-green-bg);
}

.context-badge-return:hover {
background-color: var(--bugc-accent-green);
color: var(--bugc-bg-primary);
}

.context-badge-revert {
color: var(--bugc-accent-red);
background-color: rgba(207, 34, 46, 0.1);
}

.context-badge-revert:hover {
background-color: var(--bugc-accent-red);
color: var(--bugc-bg-primary);
}

/* Inline context labels */
.context-label {
font-size: 0.75rem;
font-style: italic;
margin-left: 0.5rem;
}

.context-label-invoke {
color: var(--bugc-accent-blue);
}

.context-label-return {
color: var(--bugc-accent-green);
}

.context-label-revert {
color: var(--bugc-accent-red);
}

/* Highlight rows with call contexts */
.opcode-line.context-invoke {
border-left: 2px solid var(--bugc-accent-blue);
padding-left: calc(0.5rem - 2px);
}

.opcode-line.context-return {
border-left: 2px solid var(--bugc-accent-green);
padding-left: calc(0.5rem - 2px);
}

.opcode-line.context-revert {
border-left: 2px solid var(--bugc-accent-red);
padding-left: calc(0.5rem - 2px);
}
102 changes: 96 additions & 6 deletions packages/bugc-react/src/components/BytecodeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,29 @@
import React from "react";
import type { Evm } from "@ethdebug/bugc";
import type { BytecodeOutput, SourceRange } from "#types";
import { extractSourceRange } from "#utils/debugUtils";
import {
extractSourceRange,
classifyContext,
summarizeContext,
type ContextKind,
type DeclarationRange,
} from "#utils/debugUtils";
import { useEthdebugTooltip } from "#hooks/useEthdebugTooltip";
import { EthdebugTooltip } from "./EthdebugTooltip.js";

function contextBadgeLabel(kind: ContextKind): string {
switch (kind) {
case "invoke":
return "\u279c"; // arrow right
case "return":
return "\u21b5"; // return arrow
case "revert":
return "\u2717"; // x mark
default:
return "\u2139"; // info
}
}

/**
* Props for BytecodeView component.
*/
Expand All @@ -17,16 +36,20 @@ export interface BytecodeViewProps {
bytecode: BytecodeOutput;
/** Callback when hovering over an opcode with source ranges */
onOpcodeHover?: (ranges: SourceRange[]) => void;
/** Callback when clicking a context badge with a declaration */
onDeclarationClick?: (decl: DeclarationRange) => void;
}

interface InstructionsViewProps {
instructions: Evm.Instruction[];
onOpcodeHover?: (ranges: SourceRange[]) => void;
onDeclarationClick?: (decl: DeclarationRange) => void;
}

function InstructionsView({
instructions,
onOpcodeHover,
onDeclarationClick,
}: InstructionsViewProps): JSX.Element {
const {
tooltip,
Expand All @@ -47,12 +70,35 @@ function InstructionsView({
onOpcodeHover?.([]);
};

const formatTooltipContent = (instruction: Evm.Instruction): string => {
const ctx = instruction.debug?.context;
if (!ctx) return "";

const summary = summarizeContext(ctx);
const lines: string[] = [];

if (
summary.kind === "invoke" ||
summary.kind === "return" ||
summary.kind === "revert"
) {
lines.push(summary.label);
if (summary.details) {
lines.push(` (${summary.details})`);
}
lines.push("");
}

lines.push(JSON.stringify(ctx, null, 2));
return lines.join("\n");
};

const handleDebugIconMouseEnter = (
e: React.MouseEvent<HTMLSpanElement>,
instruction: Evm.Instruction,
) => {
if (instruction.debug?.context) {
showTooltip(e, JSON.stringify(instruction.debug.context, null, 2));
showTooltip(e, formatTooltipContent(instruction));
}
};

Expand All @@ -61,7 +107,22 @@ function InstructionsView({
instruction: Evm.Instruction,
) => {
if (instruction.debug?.context) {
pinTooltip(e, JSON.stringify(instruction.debug.context, null, 2));
pinTooltip(e, formatTooltipContent(instruction));
}
};

const handleBadgeClick = (
e: React.MouseEvent<HTMLSpanElement>,
instruction: Evm.Instruction,
) => {
const ctx = instruction.debug?.context;
if (!ctx) return;

const summary = summarizeContext(ctx);
if (summary.declaration && onDeclarationClick) {
onDeclarationClick(summary.declaration);
} else {
pinTooltip(e, formatTooltipContent(instruction));
}
};

Expand All @@ -73,22 +134,43 @@ function InstructionsView({

const sourceRanges = extractSourceRange(instruction.debug?.context);
const hasDebugInfo = !!instruction.debug?.context;
const kind: ContextKind = hasDebugInfo
? classifyContext(instruction.debug?.context)
: "other";
const isCallContext =
kind === "invoke" || kind === "return" || kind === "revert";

return (
<div
key={idx}
className={`opcode-line ${hasDebugInfo ? "has-debug-info" : ""}`}
className={[
"opcode-line",
hasDebugInfo ? "has-debug-info" : "",
isCallContext ? `context-${kind}` : "",
]
.filter(Boolean)
.join(" ")}
onMouseEnter={() => handleOpcodeMouseEnter(sourceRanges)}
onMouseLeave={handleOpcodeMouseLeave}
>
{hasDebugInfo ? (
{isCallContext ? (
<span
className={`context-badge context-badge-${kind}`}
onMouseEnter={(e) => handleDebugIconMouseEnter(e, instruction)}
onMouseLeave={hideTooltip}
onClick={(e) => handleBadgeClick(e, instruction)}
title={summarizeContext(instruction.debug?.context).label}
>
{contextBadgeLabel(kind)}
</span>
) : hasDebugInfo ? (
<span
className="debug-info-icon"
onMouseEnter={(e) => handleDebugIconMouseEnter(e, instruction)}
onMouseLeave={hideTooltip}
onClick={(e) => handleDebugIconClick(e, instruction)}
>
{"\u2139"}
</span>
) : (
<span className="debug-info-spacer"></span>
Expand All @@ -103,6 +185,11 @@ function InstructionsView({
.join("")}
</span>
)}
{isCallContext && (
<span className={`context-label context-label-${kind}`}>
{summarizeContext(instruction.debug?.context).label}
</span>
)}
</div>
);
})}
Expand Down Expand Up @@ -135,6 +222,7 @@ function InstructionsView({
export function BytecodeView({
bytecode,
onOpcodeHover,
onDeclarationClick,
}: BytecodeViewProps): JSX.Element {
const runtimeHex = Array.from(bytecode.runtime)
.map((b) => b.toString(16).padStart(2, "0"))
Expand Down Expand Up @@ -169,6 +257,7 @@ export function BytecodeView({
<InstructionsView
instructions={bytecode.createInstructions}
onOpcodeHover={onOpcodeHover}
onDeclarationClick={onDeclarationClick}
/>
)}
</div>
Expand Down Expand Up @@ -196,6 +285,7 @@ export function BytecodeView({
<InstructionsView
instructions={bytecode.runtimeInstructions}
onOpcodeHover={onOpcodeHover}
onDeclarationClick={onDeclarationClick}
/>
</div>
</div>
Expand Down
6 changes: 6 additions & 0 deletions packages/bugc-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ export {
extractSourceRange,
formatDebugContext,
hasSourceRange,
classifyContext,
summarizeContext,
formatCallSignature,
type ContextKind,
type ContextSummary,
type DeclarationRange,
// IR debug utilities
extractInstructionDebug,
extractTerminatorDebug,
Expand Down
Loading
Loading