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.Y in its main-file header and the symbol shipped in Core at or before that version (table covers common symbols such as WP_Ability @ 6.9, wp_generate_uuid4 @ 4.7, wp_timezone @ 5.3).
  • The plugin’s main file contains an unconditional require / require_once of a well-known vendor bootstrap (e.g. vendor/woocommerce/action-scheduler/action-scheduler.php). Requires inside an if ( ! class_exists(…) ) { … } block are ignored.
  • composer.json lists a known package under require or require-dev whose symbols the detector recognizes (e.g. woocommerce/action-scheduler).

Both direct and negated guards are reported:

php
if ( ! class_exists( 'WP_Ability' ) ) { return; } // flagged
if ( function_exists( 'as_schedule_single_action' ) ) { … }  // flagged when AS is bootstrapped

Finding output

  • convention: dead_guard
  • kind: dead_guard
  • severity: 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.