Skip to content

fix(analyzer): prevent debug panic from stale by-reference counters#1242

Merged
azjezz merged 5 commits intocarthage-software:mainfrom
npo-mmenke:fix/analyzer-reference-count-sync
Mar 13, 2026
Merged

fix(analyzer): prevent debug panic from stale by-reference counters#1242
azjezz merged 5 commits intocarthage-software:mainfrom
npo-mmenke:fix/analyzer-reference-count-sync

Conversation

@npo-mmenke
Copy link
Copy Markdown
Contributor

Summary

This fixes a debug-only analyzer panic triggered on large WordPress analysis runs:

No references found for variable ..., even though it has a reference count of 1

Root cause: references_in_scope entries could be removed in reconciliation paths without keeping referenced_counts in sync.

Changes

  • Keep by-reference counters consistent when pruning references in reconciler::reconcile_keyed_types.
    • decrement counters before removing references_in_scope entries
    • iterate over a snapshot of local keys to allow safe mutation during cleanup
  • Make BlockContext::remove_possible_reference resilient to stale counter state.
    • if counter says > 0 but no references are present, clean up the stale counter instead of panicking
    • keep counter cleanup consistent when references collapse to a new primary reference
  • Add regression unit test:
    • remove_possible_reference_tolerates_stale_reference_count

Validation

  • cargo test -p mago-analyzer --locked
  • Reproduced and re-ran WordPress debug analysis with php-toolchain-benchmarks config:
    • command completed without panic
    • output finished with regular issue counts (error/warning/help/note)

@azjezz
Copy link
Copy Markdown
Member

azjezz commented Feb 27, 2026

How did you trigger this panic exactly? I put there on purpose, because I wanted to be able to replicate this somehow.

I know it panics on wordpress with dev builds, but never managed to reproduce.

  1. Please restore the panic, it is intentional, and this is not the only place where we panic in debug mode on purpose.
  2. If the other changes do fix the problem, then you should add a test case to reproduce the bug, and in that case, the debug won't be triggered.

@azjezz
Copy link
Copy Markdown
Member

azjezz commented Feb 27, 2026

@npo-mmenke please add a PHP code test case in crates/analyzer/tests/cases if you actually managed to reproduce the panic, i have no idea what this change is doing now or what its meant to solve.

@npo-mmenke
Copy link
Copy Markdown
Contributor Author

How did you trigger this panic exactly? I put there on purpose, because I wanted to be able to replicate this somehow.

I know it panics on wordpress with dev builds, but never managed to reproduce.

I can reproduce the panic reliably on an unpatched debug build of main.

Setup:

  • Mago commit: 609b5de8 (or current main before this fix)
  • Build: debug (cargo build -p mago)
  • Benchmark repo: php-toolchain-benchmarks (WordPress workspace + config)

Exact reproduction steps:

# in mago repo
cargo build -p mago

# run from mago repo
/usr/bin/time -f 'wall_s=%e max_rss_kb=%M' \
  target/debug/mago \
  --workspace ../php-toolchain-benchmarks/workspace/wordpress \
  --config ../php-toolchain-benchmarks/project-configurations/wordpress/mago.toml \
  analyze --reporting-format=count

Result on my side:

- exit code: 101
- panic site: crates/analyzer/src/context/block.rs:508:13
- sample panic messages:
    - No references found for variable $info['flv']['framecount'], even though it has a reference count of 1
    - No references found for variable $info['ogg']['comments_raw'], even though it has a reference count of 1
    - No references found for variable $thisfile_riff_WAVE['rgad'][0]['data'], even though it has a reference count of 1

@azjezz
Copy link
Copy Markdown
Member

azjezz commented Feb 27, 2026

The problem is we don't have an isolated case where we can reproduce this, i am aware of the panic in wordpress, it was the reason i initially added it there when adding support for references, while the changes may remove the panic, i have no idea if this is the proper fix, and as long as we dont have an isolated test to reproduce it, we can't fix it.

@npo-mmenke
Copy link
Copy Markdown
Contributor Author

working on a reproducer

Restore the intentional debug_assert in remove_possible_reference.

Add an isolated PHP regression case for the stale by-reference counter panic (issue_1242_reference_count_sync).

Register the new analyzer test case in tests/mod.rs.
@npo-mmenke
Copy link
Copy Markdown
Contributor Author

npo-mmenke commented Feb 27, 2026

Updated in the latest commit:

  • Restored the intentional debug assert in BlockContext::remove_possible_reference (no tolerant fallback).
  • Added an isolated PHP reproducer in crates/analyzer/tests/cases/issue_1242_reference_count_sync.php.
  • Registered it in crates/analyzer/tests/mod.rs.

Validation:

  • Unpatched main reproduces the panic (No references found for variable ..., exit code 101) with this minimal case.
  • This branch passes cargo test -p mago-analyzer --locked issue_1242_reference_count_sync -- --nocapture.
  • Full analyzer suite passes: cargo test -p mago-analyzer --locked (958 passed, 0 failed).

Add a regression unit test for reference chains ( =& ;  =& ).

When removing a referenced variable, keep existing counts on the promoted reference and add reassigned references instead of dropping the count.
Add unit tests for promoted-reference count handling and debug stale-count panic behavior.

Add two integration cases for reconciler reference graph paths and register them in tests/mod.rs.
@npo-mmenke
Copy link
Copy Markdown
Contributor Author

Added targeted test coverage for the reference-count sync changes.

What was added:

  • Unit tests in crates/analyzer/src/context/block.rs for:
    • promoted reference count preservation when a root is removed
    • len > 1 promotion paths (with and without existing promoted count)
    • debug-only stale-count panic behavior (#[should_panic] with #[cfg(debug_assertions)])
  • Integration test cases:
    • crates/analyzer/tests/cases/issue_1242_reconciler_reference_graph_single.php
    • crates/analyzer/tests/cases/issue_1242_reconciler_reference_graph_snapshot.php
  • Registered both in crates/analyzer/tests/mod.rs

Local validation:

  • cargo test -p mago-analyzer --locked remove_possible_reference_ -- --nocapture
  • cargo test -p mago-analyzer --locked issue_1242_reference_count_sync -- --nocapture
  • cargo test -p mago-analyzer --locked issue_1242_reconciler_reference_graph_ -- --nocapture
  • cargo test -p mago-analyzer --locked

All passed.

Comment thread crates/analyzer/tests/mod.rs Outdated
@npo-mmenke npo-mmenke requested a review from azjezz March 11, 2026 11:55
@azjezz azjezz merged commit 83adcc8 into carthage-software:main Mar 13, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants