Skip to content

perf(metrics): bound the cognitive nesting map #82

perf(metrics): bound the cognitive nesting map

perf(metrics): bound the cognitive nesting map #82

Workflow file for this run

name: Fuzz
# cargo-fuzz targets for the parse-and-walk layer (#1154).
#
# A separate workflow rather than a job in ci.yml, for one concrete
# reason: cargo-fuzz needs nightly, and ci.yml sets
# `RUSTFLAGS: "-D warnings"` at the workflow level. Pairing the two turns
# every new nightly lint into a red X on unrelated PRs — the same
# reasoning the `coverage` job in ci.yml records for staying on stable.
# Here the flag simply is not set, so a nightly lint is a warning.
#
# The consequence is worth stating plainly: this workflow is not in the
# `ci` aggregator's `needs:`, so it is advisory until branch protection
# names it.
#
# See docs/development/fuzzing.md.
# No `push:` trigger. A merge re-runs the identical tree the pull request
# already validated, so it costs ~17 minutes to learn nothing.
on:
pull_request:
paths:
- 'src/**'
- 'fuzz/**'
- 'Cargo.toml'
- 'Cargo.lock'
- 'tree-sitter-*/**'
- 'Makefile'
- '.github/workflows/fuzz.yml'
schedule:
# 07:00 UTC on the 8th of January, April, July, and October — a week
# after the mutation-testing cron and a week before the benchmark
# one, so the three out-of-band jobs never contend.
- cron: '0 7 8 1,4,7,10 *'
workflow_dispatch:
permissions:
contents: read
issues: write
concurrency:
group: fuzz-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
# All `uses:` below are pinned to commit SHAs to eliminate supply-chain
# risk from action-release spoofing. Bump via Dependabot (see
# .github/dependabot.yml), which rewrites the SHA + version comment.
#
# Note the absence of `RUSTFLAGS: "-D warnings"` — see the header.
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
CARGO_NET_RETRY: 10
RUSTUP_MAX_RETRIES: 10
jobs:
fuzz:
name: Build and smoke targets
runs-on: ubuntu-latest
timeout-minutes: 180
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1
# The integration corpora under tests/repositories/ are for the
# snapshot suites; no fuzz target reads them.
submodules: false
# `components:` is not optional here. The action installs a minimal
# profile, so a bare `toolchain: nightly` has neither rustfmt nor
# clippy — and `make fuzz-check` opens with a fmt stage and follows
# it with clippy, so the job dies on its first command with
# "'cargo-fmt' is not installed for the toolchain 'nightly-...'".
# It passes locally, where nightly was installed by hand with both.
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # stable tip
with:
toolchain: nightly
components: rustfmt, clippy
# A distinct cache key: these artifacts are built with
# `-Zsanitizer=address` and ASan-instrumented C, so they are not
# interchangeable with the regular build cache. Same reasoning as
# the `coverage` job's `key: coverage` in ci.yml.
- name: Cache cargo build artifacts
uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
key: fuzz
workspaces: |
.
fuzz
- name: Install cargo-fuzz
uses: taiki-e/install-action@37f7c5781271959fb65b6b35224e28652ff2b63d # v2.87.0
with:
tool: cargo-fuzz
# LSan matches a `leak:` suppression against the *symbolized*
# stack, so with no symbolizer reachable every entry in
# fuzz/lsan-suppressions.txt silently stops applying and the known
# tree-sitter-perl leak fails the run. That is not theoretical —
# it is how this job first went red while passing locally, where
# the distro ships `/usr/bin/llvm-symbolizer`. `rustup component
# add llvm-tools` does not provide one.
#
# The runner image carries versioned copies under
# `/usr/lib/llvm-*/bin`, which the Makefile resolver finds, so the
# install below is a fallback rather than the normal path.
- name: Ensure an llvm-symbolizer is available
run: |
set -euo pipefail
# Glob + `-x` rather than `ls`: the Makefile resolver looks in
# both places, so this must agree with it, and a non-matching
# glob stays literal (hence the executable test).
sym="$(command -v llvm-symbolizer || true)"
if [ -z "$sym" ]; then
for candidate in /usr/lib/llvm-*/bin/llvm-symbolizer; do
[ -x "$candidate" ] && sym="$candidate"
done
fi
if [ -n "$sym" ]; then
echo "symbolizer present: $sym"
else
echo "no symbolizer found; installing llvm"
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends llvm
fi
# Also the fuzz crate's only lint gate: it is workspace-excluded,
# so `cargo clippy --workspace` in ci.yml cannot reach it (#164,
# #1228).
- name: Lint and build fuzz targets
run: make fuzz-check
# A pull request replays the committed seeds and stops: the
# regression question ("did this change break an input that used to
# be fine") rather than the hunt. Seconds, against the ~16 minutes
# 11 targets x 10 000 mutations used to cost — and that spend bought
# a search too short to find anything the cron will not.
#
# The cron does the real fuzzing. `-runs`, never `-max_total_time`:
# a wall-clock bound makes a failure non-reproducible across
# runners of different speeds.
- name: Fuzz the targets
id: smoke
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
make fuzz-replay
else
make fuzz-smoke FUZZ_RUNS=200000
fi
# libFuzzer writes the offending input to fuzz/artifacts/<target>/.
# Without this the reproducer dies with the runner.
- name: Upload crash artifacts
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: fuzz-artifacts
path: fuzz/artifacts/
if-no-files-found: warn
retention-days: 90
# Only for the cron: nobody is watching a scheduled run, so a red X
# alone would sit unread. A PR run is already reported on the PR.
- name: File issue on a scheduled-run crash
if: failure() && github.event_name == 'schedule' && steps.smoke.conclusion == 'failure'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
set -euo pipefail
if [ ! -d fuzz/artifacts ] || [ -z "$(ls -A fuzz/artifacts 2>/dev/null)" ]; then
echo "The smoke step failed without writing a reproducer;"
echo "treating as infrastructure failure, not opening an issue."
exit 0
fi
{
echo "The scheduled \`fuzz.yml\` run produced at least one crash."
echo
echo "- Run: ${RUN_URL}"
echo
echo "Reproducers are in the \`fuzz-artifacts\` artifact, one"
echo "directory per target. To reproduce locally — via the make"
echo "targets, which carry the sanitizer flags that instrument"
echo "the tree-sitter C scanners:"
echo
echo '```bash'
echo 'make fuzz-run FUZZ_TARGET=<target> FUZZ_INPUT=fuzz/artifacts/<target>/<file>'
echo 'make fuzz-tmin FUZZ_TARGET=<target> FUZZ_INPUT=fuzz/artifacts/<target>/<file>'
echo '```'
echo
echo "Per \`docs/development/fuzzing.md\`, a confirmed crasher"
echo "becomes both a committed corpus seed and a normal"
echo "\`#[test]\` — the test is what enforces the regression"
echo "without anyone re-running the fuzzer."
} > /tmp/fuzz-issue-body.md
gh issue create \
--title "Fuzzing: crash in scheduled run ${{ github.run_id }}" \
--label "bug" \
--body-file /tmp/fuzz-issue-body.md