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 ¶
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 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 ¶
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) Bytes ¶
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.