Dead Guard Detection
Homeboy audit flags function_exists('…'), class_exists('…'), and defined('…') guards whose
checked symbol is guaranteed to exist at runtime. Such guards are reachable-but-dead: the else
branch can never fire, the code becomes harder to read, and refactors keep carrying forward stale
defensive scaffolding.
How the detector decides a symbol is guaranteed
A symbol is considered guaranteed when any of the following hold:
- The plugin declares
Requires at least: X.Yin its main-file header and the symbol shipped in Core at or before that version (table covers common symbols such asWP_Ability@ 6.9,wp_generate_uuid4@ 4.7,wp_timezone@ 5.3). - The plugin’s main file contains an unconditional
require/require_onceof a well-known vendor bootstrap (e.g.vendor/woocommerce/action-scheduler/action-scheduler.php). Requires inside anif ( ! class_exists(…) ) { … }block are ignored. composer.jsonlists a known package underrequireorrequire-devwhose symbols the detector recognizes (e.g.woocommerce/action-scheduler).
Both direct and negated guards are reported:
if ( ! class_exists( 'WP_Ability' ) ) { return; } // flagged
if ( function_exists( 'as_schedule_single_action' ) ) { … } // flagged when AS is bootstrappedFinding output
convention:dead_guardkind:dead_guardseverity:warning
Dead-guard findings participate in baseline comparisons like any other audit finding.
Context comments
Extensions can configure audit.detector_rules.dead_guard_context_comment_patterns with regexes that
mark a specific guard as intentionally dual-context. Core only matches the configured regexes against
comments near the guard; extensions own the wording and domain conventions.
The comment context is the guard line plus the guarded if arm when it has braces, optionally prefixed
by the immediately preceding comment block. Non-comment code and string literals are not matched.
Extending the symbol table
The WP-core symbol table lives in src/core/code_audit/requirements.rs. Add new rows as
(symbol_name, introduced_in_encoded_version, kind) — kind is 'f' for functions, 'c' for
classes, 'k' for constants. Version is encoded as major * 100 + minor (e.g. 6.9 → 609).
Vendor packages are seeded via seed_vendor_symbols_from_path (bootstrap-require match) and
apply_composer_requires (composer.json match). Add new packages there.