diff --git a/packages/format/src/types/program/context.test.ts b/packages/format/src/types/program/context.test.ts index 12ba9c2a..65cee6de 100644 --- a/packages/format/src/types/program/context.test.ts +++ b/packages/format/src/types/program/context.test.ts @@ -18,4 +18,16 @@ testSchemaGuards("ethdebug/format/program/context", [ schema: "schema:ethdebug/format/program/context/remark", guard: Context.isRemark }, + { + schema: "schema:ethdebug/format/program/context/pick", + guard: Context.isPick + }, + { + schema: "schema:ethdebug/format/program/context/gather", + guard: Context.isGather + }, + { + schema: "schema:ethdebug/format/program/context/frame", + guard: Context.isFrame + }, ] as const); diff --git a/packages/format/src/types/program/context.ts b/packages/format/src/types/program/context.ts index 8fd027cc..74b2bde2 100644 --- a/packages/format/src/types/program/context.ts +++ b/packages/format/src/types/program/context.ts @@ -5,12 +5,18 @@ import { Pointer, isPointer } from "../pointer"; export type Context = | Context.Code | Context.Variables - | Context.Remark; + | Context.Remark + | Context.Pick + | Context.Gather + | Context.Frame; export const isContext = (value: unknown): value is Context => [ Context.isCode, Context.isVariables, Context.isRemark, + Context.isPick, + Context.isFrame, + Context.isGather, ].some(guard => guard(value)); export namespace Context { @@ -76,4 +82,30 @@ export namespace Context { export const isRemark = (value: unknown): value is Remark => typeof value === "object" && !!value && "remark" in value && typeof value.remark === "string"; + + export interface Pick { + pick: Context[]; + } + + export const isPick = (value: unknown): value is Pick => + typeof value === "object" && !!value && + "pick" in value && Array.isArray(value.pick) && + value.pick.every(isContext); + + export interface Gather { + gather: Context[]; + } + + export const isGather = (value: unknown): value is Gather => + typeof value === "object" && !!value && + "gather" in value && Array.isArray(value.gather) && + value.gather.every(isContext); + + export interface Frame { + frame: string; + } + + export const isFrame = (value: unknown): value is Frame => + typeof value === "object" && !!value && + "frame" in value && typeof value.frame === "string"; } diff --git a/packages/web/spec/program/context/frame.mdx b/packages/web/spec/program/context/frame.mdx new file mode 100644 index 00000000..18bef1e6 --- /dev/null +++ b/packages/web/spec/program/context/frame.mdx @@ -0,0 +1,11 @@ +--- +sidebar_position: 7 +--- + +import SchemaViewer from "@site/src/components/SchemaViewer"; + +# Frame contexts + + diff --git a/packages/web/spec/program/context/gather.mdx b/packages/web/spec/program/context/gather.mdx new file mode 100644 index 00000000..fcb4f9aa --- /dev/null +++ b/packages/web/spec/program/context/gather.mdx @@ -0,0 +1,11 @@ +--- +sidebar_position: 7 +--- + +import SchemaViewer from "@site/src/components/SchemaViewer"; + +# Gather multiple contexts + + diff --git a/packages/web/spec/program/context/pick.mdx b/packages/web/spec/program/context/pick.mdx new file mode 100644 index 00000000..0383684b --- /dev/null +++ b/packages/web/spec/program/context/pick.mdx @@ -0,0 +1,11 @@ +--- +sidebar_position: 7 +--- + +import SchemaViewer from "@site/src/components/SchemaViewer"; + +# Pick one of several contexts + + diff --git a/packages/web/spec/program/example.mdx b/packages/web/spec/program/example.mdx index cddcd823..b3c17892 100644 --- a/packages/web/spec/program/example.mdx +++ b/packages/web/spec/program/example.mdx @@ -28,6 +28,7 @@ code { let localValue = storedValue + 1; storedValue = localValue; + return; } `}]} instructions={[ @@ -285,8 +286,10 @@ code { mnemonic: "JUMPDEST" }, context: ({ findSourceRange }) => ({ - code: findSourceRange("return;"), - remark: "skip to here if not enough paid" + pick: [ + { code: findSourceRange("return;") }, + { code: findSourceRange("return;", { after: "return;" }) }, + ] }) } ]} diff --git a/packages/web/src/schemas.ts b/packages/web/src/schemas.ts index 0364606d..cdeff995 100644 --- a/packages/web/src/schemas.ts +++ b/packages/web/src/schemas.ts @@ -215,7 +215,7 @@ const programSchemaIndex: SchemaIndex = { }, ...( - ["code", "variables", "remark"].map(name => ({ + ["code", "variables", "remark", "pick", "gather", "frame"].map(name => ({ [`schema:ethdebug/format/program/context/${name}`]: { href: `/spec/program/context/${name}` } diff --git a/packages/web/src/theme/ProgramExample/Details.tsx b/packages/web/src/theme/ProgramExample/Details.tsx new file mode 100644 index 00000000..46ba0f33 --- /dev/null +++ b/packages/web/src/theme/ProgramExample/Details.tsx @@ -0,0 +1,67 @@ +import Admonition from "@theme/Admonition"; +import Link from "@docusaurus/Link"; +import { Program } from "@ethdebug/format"; +import { useProgramExampleContext } from "./ProgramExampleContext"; +import { HighlightedInstruction } from "./HighlightedInstruction"; +import { Variables } from "./Variables"; + +// imported for style legend +import "./SourceContents.css"; + +export interface Props { +} + +export function Details(props: Props): JSX.Element { + const { highlightedInstruction, highlightMode } = useProgramExampleContext(); + + if (highlightMode === "simple" || !highlightedInstruction) { + return <> +

Details

+ + ; + } + + + return <> +

Details

+ +
+ See full ethdebug/format/program/instruction object + +
+ ; +} + +interface InstructionAdmonitionProps { + instruction: Program.Instruction; +} +function InstructionAdmonition({ + instruction +}: InstructionAdmonitionProps): JSX.Element { + return +

+ The selected instruction provides the following + ethdebug/format Program contexts + : +

+
    +
  • + Code context is highlighted in this + style above. +
  • +
  • + Variables context is indicated by variable declarations + highlighted in this + style above. +
  • +
+
; +} + +function BasicAdmonition(props: {}): JSX.Element { + return + Select an instruction offset to see associated + ethdebug/format + debugging information. + ; +} diff --git a/packages/web/src/theme/ProgramExample/SourceContents.css b/packages/web/src/theme/ProgramExample/SourceContents.css index 68912dd9..c07d3689 100644 --- a/packages/web/src/theme/ProgramExample/SourceContents.css +++ b/packages/web/src/theme/ProgramExample/SourceContents.css @@ -4,6 +4,11 @@ background-color: var(--ifm-color-primary-lightest); } +.highlighted-ambiguous-code { + font-weight: bold; + background-color: var(--ifm-color-warning-lightest); +} + .highlighted-variable-declaration { text-decoration: underline; text-decoration-style: wavy; diff --git a/packages/web/src/theme/ProgramExample/SourceContents.tsx b/packages/web/src/theme/ProgramExample/SourceContents.tsx index b076f558..40b0dc43 100644 --- a/packages/web/src/theme/ProgramExample/SourceContents.tsx +++ b/packages/web/src/theme/ProgramExample/SourceContents.tsx @@ -31,7 +31,9 @@ export function SourceContents( const simpleDecorations = Program.Context.isCode(context) ? decorateCodeContext(context, source) - : []; + : Program.Context.isPick(context) + ? decoratePickContext(context, source) + : []; const detailedDecorations = [ ...simpleDecorations, @@ -56,7 +58,8 @@ export function SourceContents( function decorateCodeContext( { code }: Program.Context.Code, - source: Materials.Source + source: Materials.Source, + className: string = "highlighted-code" ): Shiki.DecorationItem[] { const { offset, length } = normalizeRange(code.range, source); @@ -65,12 +68,29 @@ function decorateCodeContext( start: offset, end: offset + length, properties: { - class: "highlighted-code" + class: className } } ]; } +function decoratePickContext( + { pick }: Program.Context.Pick, + source: Materials.Source +): Shiki.DecorationItem[] { + // HACK this only supports picking from a choice of several different code + // contexts + if (!pick.every(Program.Context.isCode)) { + console.warn("decoratePickContext encountered non-code contexts in pick array. These will be ignored."); + return []; + } + + return pick.flatMap( + (choice) => decorateCodeContext(choice, source, "highlighted-ambiguous-code") + ); +} + + function decorateVariablesContext( { variables }: Program.Context.Variables, source: Materials.Source diff --git a/packages/web/src/theme/ProgramExample/Viewer.tsx b/packages/web/src/theme/ProgramExample/Viewer.tsx index 91fd137b..017acb6c 100644 --- a/packages/web/src/theme/ProgramExample/Viewer.tsx +++ b/packages/web/src/theme/ProgramExample/Viewer.tsx @@ -1,60 +1,16 @@ import Admonition from "@theme/Admonition"; import Link from "@docusaurus/Link"; -import { useProgramExampleContext } from "./ProgramExampleContext"; import { SourceContents } from "./SourceContents"; import { Opcodes } from "./Opcodes"; -import { HighlightedInstruction } from "./HighlightedInstruction"; +import { Details } from "./Details"; import { Variables } from "./Variables"; import "./Viewer.css"; -import "./SourceContents.css"; export interface Props { } export function Viewer(props: Props): JSX.Element { - const { highlightedInstruction, highlightMode } = useProgramExampleContext(); - - const basicAdmonition = - Select an instruction offset to see associated - ethdebug/format - debugging information. - ; - - const detailedAdmonition = -

- The selected instruction provides the following - ethdebug/format Program contexts - : -

-
    -
  • - Code context is highlighted in this - style above. -
  • -
  • - Variables context is indicated by variable declarations - highlighted in this - style above. -
  • -
-
; - - const details = highlightedInstruction && highlightMode === "detailed" - ? <> -

Details

- {detailedAdmonition} -
- See full ethdebug/format/program/instruction object - -
- - : <> -

Details

- {basicAdmonition} - ; - - return <>

Interactive example

@@ -67,6 +23,6 @@ export function Viewer(props: Props): JSX.Element {
- {details} +
; } diff --git a/schemas/program/context.schema.yaml b/schemas/program/context.schema.yaml index 18a67db4..5739ff40 100644 --- a/schemas/program/context.schema.yaml +++ b/schemas/program/context.schema.yaml @@ -7,17 +7,21 @@ description: | information about the high-level runtime execution state at a specific point in a program's bytecode. - This schema defines the structure for encoding compile-time knowledge about - the high-level runtime state. Contexts are treated as units of information - that may encompass multiple related or unrelated facts about the program's - state. This may include, e.g., source mapping information (via the `"code"` - property) or information about known variable allocations, etc. - - The interpretation of a context depends on its properties and its - relationship to program elements such as instructions or control flow - structures. For example, a context associated with an instruction may - indicate that the specified conditions hold true following the execution of - that instruction. + This schema provides a formal specification for this format's model of what + information can be known at compile-time about the high-level runtime. This + includes data such as a particular machine instruction's source mapping or + what variables exist in runtime state following some instruction. + + The context object supports dynamic context combination and selection through + the use of `gather`, and `pick` properties. This allows for flexible + composition and extraction of context information. + + Contexts serve as a bridge between low-level EVM execution and high-level + language constructs. Debuggers can use these compile-time guarantees to + maintain a coherent view of the high-level language runtime throughout + program execution. This enables debugging tools to map execution points to + source code, reconstruct variable states, provide meaningful stack traces, + and offer insights into control flow and data structures. type: object @@ -34,6 +38,18 @@ allOf: required: ["remark"] then: $ref: "schema:ethdebug/format/program/context/remark" + - if: + required: ["pick"] + then: + $ref: "schema:ethdebug/format/program/context/pick" + - if: + required: ["gather"] + then: + $ref: "schema:ethdebug/format/program/context/gather" + - if: + required: ["frame"] + then: + $ref: "schema:ethdebug/format/program/context/frame" unevaluatedProperties: false diff --git a/schemas/program/context/frame.schema.yaml b/schemas/program/context/frame.schema.yaml new file mode 100644 index 00000000..797c8c06 --- /dev/null +++ b/schemas/program/context/frame.schema.yaml @@ -0,0 +1,20 @@ +$schema: "https://json-schema.org/draft/2020-12/schema" +$id: "schema:ethdebug/format/program/context/frame" + +title: ethdebug/format/program/context/frame +description: | + A context may specify a `"frame"` property to indicate that its facts apply + only to one of several possible compilation frames, e.g. for compilers with + distinct frontend/backends to specify debugging data for the IR separately + from the debugging data for the source language. +type: object +properties: + frame: + title: Relevant compilation frame + type: string +required: + - frame + +examples: + - frame: "ir" + - frame: "source" diff --git a/schemas/program/context/gather.schema.yaml b/schemas/program/context/gather.schema.yaml new file mode 100644 index 00000000..59c5768b --- /dev/null +++ b/schemas/program/context/gather.schema.yaml @@ -0,0 +1,58 @@ +$schema: "https://json-schema.org/draft/2020-12/schema" +$id: "schema:ethdebug/format/program/context/gather" + +title: ethdebug/format/program/context/gather +description: | + A context specifying the `"gather"` property with a list of contexts + indicates that all specified contexts apply simultaneously. + +type: object +properties: + gather: + title: Contexts to gather + type: array + items: + $ref: "schema:ethdebug/format/program/context" + minItems: 2 + additionalItems: false +required: + - gather + +examples: + - gather: + - frame: "ir" + code: + source: + id: 0 + range: + offset: 8 + length: 11 + - frame: "source" + code: + source: + id: 3 + range: + offset: 113 + length: 19 + - gather: + - variables: + - identifier: x + declaration: + source: + id: 5 + range: + offset: 10 + length: 56 + type: + kind: string + - variables: + - identifier: x + declaration: + source: + id: 5 + range: + offset: 10 + length: 56 + pointer: + location: storage + slot: 0 diff --git a/schemas/program/context/pick.schema.yaml b/schemas/program/context/pick.schema.yaml new file mode 100644 index 00000000..18cf04dd --- /dev/null +++ b/schemas/program/context/pick.schema.yaml @@ -0,0 +1,35 @@ +$schema: "https://json-schema.org/draft/2020-12/schema" +$id: "schema:ethdebug/format/program/context/pick" + +title: ethdebug/format/program/context/pick +description: | + A program context that specifies the `"pick"` property indicates that + one of several possible contexts are known to be true, possibly requiring + additional information to disambiguate. + +type: object +properties: + pick: + title: Contexts to pick from + type: array + items: + $ref: "schema:ethdebug/format/program/context" + minItems: 2 + additionalItems: false +required: + - pick + +examples: + - pick: + - code: + source: + id: 5 + range: + offset: 68 + length: 16 + - code: + source: + id: 5 + range: + offset: 132 + length: 16