lez-fuzzing/scripts/mutants-corpus-test.sh
2026-07-02 15:04:31 +02:00

61 lines
2.4 KiB
Bash
Executable File

#!/usr/bin/env bash
# Plane-B mutation-testing oracle.
#
# Called by `cargo mutants --test-command` from *inside* the logos-execution-zone
# workspace directory after each source mutation. Replays the committed
# libFuzzer corpus against every fuzz target (cargo fuzz run -runs=0).
#
# Exit behaviour (used by cargo-mutants to classify each mutant):
# exit 0 → all corpus replays passed → mutant SURVIVED (corpus gap)
# exit ≠0 → at least one replay panicked → mutant CAUGHT (corpus covers it)
#
# Environment variables:
# FUZZ_REPO absolute path to the lez-fuzzing repository root.
# Defaults to the directory one level above this script.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
FUZZ_REPO="${FUZZ_REPO:-"$(cd "${SCRIPT_DIR}/.." && pwd)"}"
CORPUS_ROOT="${FUZZ_REPO}/corpus/libfuzz"
FUZZ_DIR="${FUZZ_REPO}/fuzz"
# Derive the target list from fuzz/Cargo.toml (the single source of truth) — the same
# `[[bin]] name = "fuzz_*"` parse that .github/actions/resolve-targets uses. This keeps
# the script in sync with every workflow automatically, with no hand-maintained list.
# (A while-read loop rather than `mapfile`, so this also works under macOS' bash 3.2.)
CARGO_TOML="${FUZZ_DIR}/Cargo.toml"
targets=()
while IFS= read -r _target; do
[ -n "${_target}" ] && targets+=("${_target}")
done < <(
grep -oE 'name = "fuzz_[a-z0-9_]+"' "${CARGO_TOML}" \
| sed -E 's/.*"(fuzz_[a-z0-9_]+)"/\1/' \
| awk '!seen[$0]++'
)
if [ "${#targets[@]}" -eq 0 ]; then
echo "ERROR: no fuzz_* [[bin]] targets found in ${CARGO_TOML}" >&2
exit 1
fi
# cargo-fuzz requires the nightly toolchain (-Zsanitizer=address etc.).
# When this script is called by `cargo-mutants` the working directory is the
# LEZ workspace (logos-execution-zone/), whose rust-toolchain.toml pins the
# stable 1.x compiler. Change to the fuzzing repo so that rustup resolves
# the nightly toolchain from lez-fuzzing/rust-toolchain.toml instead.
cd "${FUZZ_REPO}"
for target in "${targets[@]}"; do
corpus="${CORPUS_ROOT}/${target}"
mkdir -p "${corpus}"
# -runs=0 → replay every file in the corpus directory exactly once, then exit.
# A panic (invariant violation) causes cargo fuzz to exit non-zero, which
# propagates through this script and causes cargo-mutants to mark the mutant
# as CAUGHT.
cargo fuzz run "${target}" \
--fuzz-dir "${FUZZ_DIR}" \
"${corpus}" \
-- -runs=0
done