fuzzval

package
v1.55.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 25, 2026 License: BSD-3-Clause Imports: 6 Imported by: 0

Documentation

Overview

Package fuzzval turns a fuzzer-supplied byte string into typed lisp.LVal arguments for the repository's builtin/stdlib fuzz targets.

WHY A VALUE GENERATOR AND NOT MORE SOURCE-LEVEL FUZZING. The parser, lexer, formatter and minifier targets (internal/fuzzseed) mutate ELPS SOURCE. Everything they can reach has to be spellable in the grammar, and several LVal shapes are not: an LNative wrapping an arbitrary Go value, a multi-dimensional LArray, an LError value passed as an ordinary argument, a tagged-value whose user data is itself a tagged-value, a sorted-map keyed by a symbol rather than a string. Those shapes DO reach builtins in practice -- host code hands natives in, json:load-bytes builds nested maps, deftype builds tagged values -- so the only way to fuzz a builtin against them is to construct the values directly and apply the function to them.

SINGLETONS ARE DELIBERATELY IN THE CORPUS. Value can return the shared Nil() / Bool(true) / Bool(false) singletons. Any builtin that writes through one of those pointers corrupts every other holder of it; that is exactly what the `elpscheck` build tag detects (see lisp/singleton.go and issue #274). Running these targets under `go test -tags elpscheck` is therefore a much stronger check than running them without it, and the harnesses additionally verify a lisp.SingletonSnapshot on every iteration so the default build catches it too.

DETERMINISM. A Gen is a pure function of its input bytes: the same []byte always yields the same values. That is what makes a saved crasher reproducible. Nothing here reads a clock, a map iteration order, or a random source.

Index

Constants

View Source
const Budget = 512

Budget caps how many LVals one Gen will construct across all calls. The generator is recursive -- a list can hold arrays that hold maps -- so without a global cap a few bytes can ask for an exponentially large value and the target spends its whole budget in the allocator rather than in the code under test. Depth alone is not enough: breadth multiplies too.

Variables

This section is empty.

Functions

func KindSeeds added in v1.50.0

func KindSeeds() [][]byte

KindSeeds returns exactly one seed per value kind.

Seeds() is a grab-bag sized for the mutator to descend from; this is the smaller, structural list a caller uses when it needs the GUARANTEE that every value shape is represented -- for instance a target crossing seeds against callables, which must not leave a builtin having seen only six of the nineteen shapes because six was the sample size.

It is derived from kindNumKinds rather than written out, so a kind added to value() is covered by every such caller without anyone remembering to update a list.

func LocatedKindSeeds added in v1.50.0

func LocatedKindSeeds() [][]byte

LocatedKindSeeds is KindSeeds with the real-source-location bit set: one seed per kind, each producing a value carrying a REAL token.Location rather than the shared synthetic one.

Kept separate from KindSeeds because the two populations cost the same and buy different things. Measured, crossing the located population against every callable moved statement coverage of lisp/lisplib not at all -- a location does not steer control flow -- while roughly doubling what the seed corpus costs on every `go test`. What it buys instead is the ONLY population internal/fuzzfp's "a real location is never replaced" rule can bite on, so a caller should spend it where that matters rather than everywhere.

func Seeds

func Seeds() [][]byte

Seeds returns the shared seed corpus for the value-driven targets.

A coverage-guided fuzzer descends from the seeds it is given, so the seeds are chosen to land on the interesting kind tags immediately rather than to be pretty: each entry is a short byte string whose leading bytes select a kind and whose remainder feeds that kind's own reads.

Types

type Gen

type Gen struct {
	// contains filtered or unexported fields
}

Gen is a deterministic LVal generator driven by a byte string.

It never runs out of input: reads past the end of the byte string return 0. A short input therefore produces a small, boring value rather than an error, which is what lets the fuzzer start from a one-byte seed and grow.

func New

func New(data []byte, env *lisp.LEnv) *Gen

New returns a Gen driven by data.

env is used only to construct tagged-values (LEnv.TaggedValue is the only supported constructor, and it stamps a source location; hand-rolling the struct literal would leave Source nil, which no real tagged-value ever has and which would make a nil-deref in the harness look like a builtin bug). A nil env is allowed and simply removes tagged-values from the corpus.

func (*Gen) Byte

func (g *Gen) Byte() byte

Byte consumes and returns one byte, or 0 once the input is exhausted.

func (*Gen) Bytes

func (g *Gen) Bytes(n int) []byte

Bytes consumes up to n bytes and returns them. The returned slice is a copy: builtins are allowed to retain what they are given, and the fuzzing engine reuses the input buffer between iterations.

func (*Gen) Intn

func (g *Gen) Intn(n int) int

Intn consumes one byte and returns a value in [0,n). Returns 0 for n <= 0.

func (*Gen) Value

func (g *Gen) Value() *lisp.LVal

Value returns one generated LVal.

The returned value may be a shared singleton (see the package doc); callers must treat every generated value as potentially shared and must not mutate it themselves.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL