NAME
App::Test::Generator::CoverageGuidedFuzzer - AFL-style coverage-guided fuzzing for App::Test::Generator
VERSION
Version 0.34
SYNOPSIS
use App::Test::Generator::CoverageGuidedFuzzer;
my $fuzzer = App::Test::Generator::CoverageGuidedFuzzer->new(
schema => $yaml_schema,
target_sub => \&My::Module::validate,
iterations => 200,
seed => 42,
);
my $report = $fuzzer->run();
$fuzzer->save_corpus('t/corpus/validate.json');
DESCRIPTION
Implements coverage-guided fuzzing on top of App::Test::Generator's existing schema-driven input generation. Instead of purely random generation it:
- 1. Generates or mutates a structured input
- 2. Runs the target sub under Devel::Cover to capture branch hits
- 3. Keeps inputs that discover new branches in a corpus
- 4. Preferentially mutates corpus entries in future iterations
This is the Perl equivalent of what AFL/libFuzzer do at the byte level, but operating on typed, schema-validated Perl data structures.
METHODS
new
Construct a new coverage-guided fuzzer.
my $fuzzer = App::Test::Generator::CoverageGuidedFuzzer->new(
schema => $yaml_schema,
target_sub => \&My::Module::validate,
iterations => 200,
seed => 42,
instance => $obj, # optional pre-built object for method calls
);
Arguments
schemaA hashref representing the parsed YAML schema for the target function. Required.
target_subA CODE reference to the function under test. Required.
iterationsNumber of fuzzing iterations to run. Optional - defaults to 100.
seedRandom seed for reproducible runs. Optional - defaults to
time().instanceAn optional pre-built object to use as the invocant when calling the target sub as a method.
Returns
A blessed hashref. Croaks if schema or target_sub is missing.
API specification
input
{
schema => { type => HASHREF },
target_sub => { type => CODEREF },
iterations => { type => SCALAR, optional => 1 },
seed => { type => SCALAR, optional => 1 },
instance => { type => OBJECT, optional => 1 },
}
output
{
type => OBJECT,
isa => 'App::Test::Generator::CoverageGuidedFuzzer',
}
run
Run the coverage-guided fuzzing loop and return a summary report.
my $report = $fuzzer->run();
printf "Branches covered: %d\n", $report->{branches_covered};
printf "Bugs found: %d\n", $report->{bugs_found};
Arguments
None beyond $self.
Returns
A hashref with keys total_iterations, interesting_inputs, corpus_size, branches_covered, bugs_found, and bugs.
API specification
input
{
self => { type => OBJECT, isa => 'App::Test::Generator::CoverageGuidedFuzzer' },
}
output
{
type => HASHREF,
keys => {
total_iterations => { type => SCALAR },
interesting_inputs => { type => SCALAR },
corpus_size => { type => SCALAR },
branches_covered => { type => SCALAR },
bugs_found => { type => SCALAR },
bugs => { type => ARRAYREF },
},
}
corpus
Return the accumulated corpus as an arrayref of hashrefs with keys input and coverage.
my $corpus = $fuzzer->corpus();
API specification
input
{ self => { type => OBJECT, isa => 'App::Test::Generator::CoverageGuidedFuzzer' } }
output
{ type => ARRAYREF }
bugs
Return bugs found as an arrayref of hashrefs with keys input and error.
my $bugs = $fuzzer->bugs();
API specification
input
{ self => { type => OBJECT, isa => 'App::Test::Generator::CoverageGuidedFuzzer' } }
output
{ type => ARRAYREF }
save_corpus
Serialise the corpus to a JSON file for replay or extension on future runs.
$fuzzer->save_corpus('t/corpus/validate.json');
Arguments
$pathPath to write the JSON corpus file. Required.
Returns
Nothing. Croaks if the file cannot be written or no JSON module is available.
Side effects
Writes a JSON file to $path.
API specification
input
{
self => { type => OBJECT, isa => 'App::Test::Generator::CoverageGuidedFuzzer' },
path => { type => SCALAR },
}
output
{ type => UNDEF }
load_corpus
Load a previously saved corpus JSON file, pre-seeding the fuzzer so it continues from where it left off.
$fuzzer->load_corpus('t/corpus/validate.json');
Arguments
$pathPath to the JSON corpus file to load. Required.
Returns
Nothing. Croaks if the file cannot be read or no JSON module is available.
Side effects
Appends loaded entries to $self->{corpus}.
API specification
input
{
self => { type => OBJECT, isa => 'App::Test::Generator::CoverageGuidedFuzzer' },
path => { type => SCALAR },
}
output
{ type => UNDEF }
AUTHOR
Nigel Horne, <njh at nigelhorne.com>
Portions of this module's initial design and documentation were created with the assistance of AI.
LICENCE AND COPYRIGHT
Copyright 2026 Nigel Horne.
Usage is subject to GPL2 licence terms. If you use it, please let me know.