Forward, all-paths walks over a function's control flow, one instruction at a time.
Three extractors used to carry their own copy of this loop — one
deciding whether a deferred reply ever read from, one whether a
monitor ref was ever read, one whether a clause head accepts any
content — and their successors/3 functions disagreed with each other
about which instructions end a path. Here the block graph decides:
within a block execution is sequential, at the block's end it takes
every edge the caller allows, and a block whose terminator returns,
raises or tail-calls has no edges to take.
The walk starts at instruction indices, not blocks, because the interesting starting points are "just after this test succeeded", which is the middle of a block as often as not.
Summary
Types
What the caller decides at each instruction: keep walking, stop this path here (its successors are not explored), or stop the whole walk with an answer.
Functions
Explores every path from starts, calling on_instr at each
instruction reached and follow? at each edge out of a block (given
the block's last instruction and the edge kind). Returns {:halted, answer} the moment a verdict halts, or {:done, visited} with the set
of instruction indices reached.
Types
@type option() :: {:on_instr, (tuple(), non_neg_integer() -> verdict())} | {:follow?, (tuple(), Argus.Cfg.Block.edge_kind() -> boolean())}
@type verdict() :: :continue | :prune | {:halt, term()}
What the caller decides at each instruction: keep walking, stop this path here (its successors are not explored), or stop the whole walk with an answer.
Functions
@spec explore(Argus.Cfg.Function.t(), [tuple()], [non_neg_integer() | nil], [option()]) :: {:halted, term()} | {:done, MapSet.t(non_neg_integer())}
Explores every path from starts, calling on_instr at each
instruction reached and follow? at each edge out of a block (given
the block's last instruction and the edge kind). Returns {:halted, answer} the moment a verdict halts, or {:done, visited} with the set
of instruction indices reached.