perf(metrics): bound the cognitive nesting map #82
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |