Documentation
¶
Overview ¶
Package fuzzgen turns a fuzzer-supplied byte script into syntactically valid ELPS source.
Why ¶
Every other fuzz target in this repository takes raw bytes. A coverage-guided mutator starting from raw bytes spends most of its budget on input that dies at the first token, so the states it reaches are the ones near the entrance: the scanner, the lexer's dispatch switch, the parser's error paths. The states BEHIND a successful parse -- comment attachment, blank-line bookkeeping, prefix-form re-sugaring, scope-aware renaming -- are where `elps fmt` and `elps minify` silently rewrite a customer's program, and raw bytes hardly ever get there.
This package generates the other kind of input: programs that are syntactically valid but semantically hostile. Deep nesting, every bracket kind, keyword and package-qualified symbols, unicode identifiers, reader macros with awkward operands, oversized literals, and comments in every position the grammar permits one.
Contract ¶
Generate's output ALWAYS parses. That is the load-bearing property: it is what lets a fuzz target treat "the parser rejected this" as a defect rather than as the expected outcome, which is how the float-exponent and trailing-backslash lexer bugs were found -- both of them rejected input the language is supposed to accept, and no raw-byte target could tell that rejection apart from the millions of legitimate ones.
The contract cuts both ways. A crasher reported by a target that asserts it is either a parser defect or a generator defect, and triage has to consider both. TestGeneratedProgramsAlwaysParse sweeps a large deterministic sample so that a generator defect is caught by `go test` rather than by a red fuzz run.
Determinism ¶
Generation is a pure function of (script, limits). The fuzzing engine records the script, not the ELPS text, so a crasher reproduces from the committed corpus entry with no generated source to store. Corpus files under testdata/fuzz are therefore scripts.
Nothing consumes randomness, wall-clock time or the environment, and every production consumes at least one script byte, so generation terminates on a finite script. Three independent bounds apply anyway -- byte budget, nesting cap and script length -- because a generator that can emit an unbounded program hands the mutator a seed that costs seconds per execution.
On dialects ¶
There is deliberately only ONE grammar here. An earlier iteration carried a second, narrower dialect defined as the intersection of the two parsers this repository used to ship; parser/regexparser is being removed, so that definition no longer names anything and a subset with no defining property is just an untested code path. If a future target needs a restricted vocabulary it should say what restricts it.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func GenerateLimited ¶
GenerateLimited renders script as ELPS source under the given limits. A nil or empty script yields the empty program, which is valid.
Types ¶
type Limits ¶
type Limits struct {
// MaxBytes is the size at which generation stops opening new constructs.
// The result may exceed it by the tail of the construct in flight plus
// the closing brackets, which is bounded by MaxDepth + one literal.
MaxBytes int
// MaxDepth is the maximum nesting depth of generated forms. Kept far
// below rdparser.DefaultMaxParseDepth: the depth guard is already
// covered by fuzzseed.Pathological, and a deep seed is expensive to
// mutate.
MaxDepth int
// MaxForms is the maximum number of top-level forms.
MaxForms int
}
Limits bounds a single generated program. All three are enforced independently: the byte budget stops a wide program, the depth cap stops a deep one, and the script itself runs out.
func DefaultLimits ¶
func DefaultLimits() Limits
DefaultLimits are the bounds the fuzz targets use. Sized so that a generated program stays in the range where a coverage-guided run manages thousands of executions per second; the oversized shapes that must also keep working live in fuzzseed.Pathological, which ordinary unit tests run once each.