diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 28099130b4..34484383ad 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -407,6 +407,28 @@ jobs: python3 scripts/gc_runtime_root_holders.py --self-test python3 scripts/gc_runtime_root_holders.py + # #8821. The poll-reach audit is static, build-free and sub-second, but it + # lived ONLY in `gc-root-dominance.yml`, which is label-gated behind + # `run-extended-tests` — so it is skipped on every PR and speaks only on + # scheduled `main` runs, after the fact. It is also an early step there, + # ahead of the compiler build, so its failure masks the corpus and checker + # that workflow exists for: red from 2026-08-15, cleared by #8823, red + # again on 2026-08-28 when #8965 added `js_string_concat_value_box`. + # + # Same argument as the holder audit above — cheap and build-free, so it + # belongs in `lint`, which IS a required context. Running it here PREVENTS + # the omission at PR time rather than reporting it days later, and leaves + # the expensive corpus arms where they are. + # + # `--self-test` first, and it is not decoration: it PLANTS a fixture that + # makes the audit report, so a green run says the detector works rather + # than that nothing happened to be wrong today. + - name: Poll-capable reach audit (GC root dominance) + if: ${{ !cancelled() }} + run: | + python3 scripts/gc_root_dominance_check.py --self-test + python3 scripts/gc_root_dominance_check.py --audit-poll-reach + # #8530. Mutable-root scanner registries are thread-local, so guarding a # registration call with a process-global Once/OnceLock/AtomicBool (or a # mutex boolean) leaves every heap after the first without those roots. diff --git a/changelog.d/9012-poll-reach-audit-in-lint.md b/changelog.d/9012-poll-reach-audit-in-lint.md new file mode 100644 index 0000000000..4ef85341ef --- /dev/null +++ b/changelog.d/9012-poll-reach-audit-in-lint.md @@ -0,0 +1,21 @@ +The GC poll-reach audit is unblocked and now runs on every PR. + +`--audit-poll-reach` was exiting 2 on `main`: #8965 added +`js_string_concat_value_box` without listing it in `POLL_CAPABLE_RUNTIME`. It is +the exact analogue of `js_string_concat_box` listed directly above it — an SSO +fast arm, then `js_string_concat_value` as the fallback (`string/concat.rs:496`), +and that callee was already listed — so it was an omission at introduction. + +That audit is an early step of `gc-root-dominance.yml`, ahead of the compiler +build, so its failure masks the corpus and checker the workflow exists for +(#8821). The issue dates the red to 2026-08-15; that was the third wave, cleared +by #8823 on 08-25, and this is a fresh recurrence from 08-28 — the second +occurrence of the pattern the list exists to catch. + +Because recurrence is the norm, the audit now also runs in `lint`. It is static, +build-free and 0.6 s including its self-test, whereas `gc-root-dominance.yml` is +label-gated behind `run-extended-tests` and therefore skipped on every PR, able +to report only after the fact on a scheduled `main` run. This is the placement +argument the runtime GC-pointer holder audit already makes in that file: cheap +and build-free, so it belongs in a required context. The expensive corpus arms +are unchanged, as are #8821's remaining questions and #8809/#8810's findings. diff --git a/scripts/gc_root_dominance_check.py b/scripts/gc_root_dominance_check.py index 8c52b5c151..d3225a33a1 100755 --- a/scripts/gc_root_dominance_check.py +++ b/scripts/gc_root_dominance_check.py @@ -1360,6 +1360,17 @@ def is_collecting(callee): # reads the caller's options object and writes the result) "js_builtin_subclass_construct", "js_tls_create_secure_context", "js_tls_secure_context_new", + # Fourth wave. #8965 added `js_string_concat_value_box` on 2026-08-28 — + # AFTER #8823 cleared the third wave — and the audit went red again the + # same day, which is the recurrence this list is designed to catch rather + # than a new kind of problem. + # + # It is the exact analogue of `js_string_concat_box` above: an SSO fast arm + # that returns content-stable bits, then `js_string_concat_value(prefix, + # value)` as the fallback (string/concat.rs:496) — and that callee is + # already listed. So the entry is an omission at introduction, not a + # judgement call. + "js_string_concat_value_box", # `js_private_brand_add` is the referent-with-no-name that NEITHER audit can # ask for: `--audit-poll-reach` only walks symbols `ALLOC_RE` matches, and # `js_private_brand_add` matches no alloc/new/create convention, so the one