// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) // SPDX-License-Identifier: BSD-3-Clause package verify import ( "fmt" "sort" ) // Block describes one basic block within a function: a maximal sequence of // instructions with a single entry point (a label or the function start) and // a single exit (a jump, conditional jump or RET). type Block struct { Offset int // byte offset within the function Label string // label name ("" for the entry block) } // Blocks identifies the basic blocks of a function from its local labels. // Each label is a potential jump target and therefore a block boundary; the // function entry (offset 0) is always a block. The blocks are returned in // ascending offset order. func (k *Kernel) Blocks(name string) ([]Block, error) { idx, ok := k.funcs[name] if !ok { return nil, fmt.Errorf("verify: function %q not found", name) } fl := k.img.Funcs[idx] blocks := []Block{{Offset: 0, Label: "(entry)"}} // Build a reverse map: offset → label name. offToLabel := make(map[int]string, len(fl.Labels)) for label, off := range fl.Labels { if off > 0 && off < fl.Size { offToLabel[off] = label } } // Collect and sort offsets. offsets := make([]int, 0, len(offToLabel)) for off := range offToLabel { offsets = append(offsets, off) } sort.Ints(offsets) for _, off := range offsets { blocks = append(blocks, Block{Offset: off, Label: offToLabel[off]}) } return blocks, nil } // BlockCount returns the number of identified basic blocks for the function. func (k *Kernel) BlockCount(name string) (int, error) { blocks, err := k.Blocks(name) if err != nil { return 0, err } return len(blocks), nil } // PathFingerprint is the observable output of one function execution: the // values written back into the result slots of the argument block. Two // executions that produce the same fingerprint took observationally // equivalent paths (though they may differ internally). type PathFingerprint struct { Results []uint64 // the result words from the arg block } // ProfilePaths runs the function with each of the given argument blocks and // collects the distinct output fingerprints. This measures path diversity: // how many observationally different execution paths the input corpus // exercises. Combined with Blocks (the static block count), it gives a // lower bound on code coverage. func (k *Kernel) ProfilePaths(name string, argSets [][]byte, resultOffsets []int) ([]PathFingerprint, error) { idx, ok := k.funcs[name] if !ok { return nil, fmt.Errorf("verify: function %q not found", name) } fl := k.img.Funcs[idx] seen := map[string]bool{} var paths []PathFingerprint for _, args := range argSets { if len(args) < fl.Args { return nil, fmt.Errorf("verify: %s: arg block too small", name) } out, err := k.CallFunc(name, args) if err != nil { return nil, err } fp := PathFingerprint{} key := "" for _, off := range resultOffsets { v := GetUint64(out, off) fp.Results = append(fp.Results, v) key += fmt.Sprintf("%016x", v) } if !seen[key] { seen[key] = true paths = append(paths, fp) } } return paths, nil }