Skip to main content

The dereference() function

Summary

The pages in this section cover the internals of the dereference() function in the @ethdebug/pointers reference implementation.

The full signature of this function is as follows:

/**
* Dereference an ethdebug/format/pointer document into a Cursor object,
* which allows inspecting machine state corresponding to the given pointer.
*
* Note that `options.state` is required if `pointer` contains any stack
* regions.
*/
declare async function dereference(
pointer: Pointer,
dereferenceOptions?: DereferenceOptions
): Promise<Cursor>;
tip

Remember from the Cursors section that a Cursor provides a view(state: Machine.State) method, which returns an ordered collection of concrete Cursor.Region objects.

DereferenceOptions

Note the optional options: DereferenceOptions argument. This argument allows for specifying additional information upfront that is necessary for viewing the cursor later. Currently, this is needed only for pointers that compose stack-located regions.

export interface DereferenceOptions {
/*
* Initial machine state
* Required for any pointers that reference the stack.
*/
state?: Machine.State;
}

Control flow architecture

The dereference() function itself performs two tasks:

  1. Create a "simple cursor": a function that takes a machine state and produces an asynchronous list of Cursor.Regions.
  2. Adapt this simple cursor to conform to the full Cursor interface

Within the process of creating this simple cursor it gets more interesting: by leveraging JavaScript's AsyncIterators, the implementation can compute regions on the fly by recursively breaking down pointers into their nested child pointers.

Since the desired end-result of dereference() is an object that can turn a pointer into its composite ordered list of concrete regions at a particular machine state, this implementation separates the concerns of generating this list from converting this list into the promised return interface.

To generate this list asynchronously on the fly, the implementation uses a stack of processing requests (which it calls "memos"), initially populated with a request to dereference the root pointer. Each memo represents a state or context change in some form: either a request to dereference a pointer or sub-pointer, a request to save a set of regions by their names, or a request to save the computed values of a set of variables by their identifiers.

The other pages in this section proceed to go into more detail.

See the full src/dereference/index.ts module
src/dereference/index.ts
import type { Pointer } from "../pointer.js";
import type { Machine } from "../machine.js";
import type { Cursor } from "../cursor.js";

import { generateRegions, type GenerateRegionsOptions } from "./generate.js";
import { createCursor } from "./cursor.js";

export interface DereferenceOptions {
/*
* Initial machine state
* Required for any pointers that reference the stack.
*/
state?: Machine.State;
}

/**
* Dereference an ethdebug/format/pointer document into a Cursor object,
* which allows inspecting machine state corresponding to the given pointer.
*
* Note that `options.state` is required if `pointer` contains any stack
* regions.
*/
export async function dereference(
pointer: Pointer,
dereferenceOptions: DereferenceOptions = {}
): Promise<Cursor> {
const options = await initializeGenerateRegionsOptions(dereferenceOptions);

// use a closure to build a simple Cursor-like interface for accepting
// a machine state and producing a collection of regions.
const simpleCursor =
(state: Machine.State): AsyncIterable<Cursor.Region> => ({
async *[Symbol.asyncIterator]() {
yield* generateRegions(pointer, { ...options, state });
}
});

return createCursor(simpleCursor);
}

/**
* Convert DereferenceOptions into the specific pieces of information that
* `generateRegions()` will potentially need.
*/
async function initializeGenerateRegionsOptions({
state: initialState
}: DereferenceOptions): Promise<Omit<GenerateRegionsOptions, "state">> {
const initialStackLength = initialState
? await initialState.stack.length
: 0n;

return { initialStackLength };
}