Skip to content

Fix infinite recursion in option access monitoring#73

Merged
ilicfilip merged 1 commit intodevelopfrom
filip/fix-infinite-recursion
Mar 11, 2026
Merged

Fix infinite recursion in option access monitoring#73
ilicfilip merged 1 commit intodevelopfrom
filip/fix-infinite-recursion

Conversation

@ilicfilip
Copy link
Copy Markdown
Collaborator

Problem

Users report a fatal error during plugin updates:

E_ERROR on line 122: Uncaught Error: Maximum call stack size reached. Infinite recursion?
in src/class-plugin.php:122

Root cause

The option access monitoring hooks (pre_option filter and all hook) have no re-entrancy protection. When our filter callback executes and anything in the call stack triggers another get_option() call, the filter fires again, creating infinite recursion.

The recursion chain:

  1. get_option('some_option') fires the pre_option filter (or all hook)
  2. Our handler runs add_option_usage()
  3. Something in the environment (another plugin, object cache drop-in, translation loading, WP core internals) triggers another get_option() call
  4. Our handler fires again → infinite loop → stack overflow

This is a latent bug since v1.0

Checked the code at v1.0, v1.5.1, and v1.6.0 — none of them had recursion protection. The all hook has always been vulnerable. The bug simply never triggered in test environments because it requires a specific combination of:

  • Another plugin or WP component calling get_option() during filter dispatch
  • Object cache drop-ins that call get_option() for connection settings
  • Translation lazy-loading triggering get_option('WPLANG')
  • WP core changes in newer versions

Fix

Adds a $is_processing flag to the Plugin class. Both monitor_option_accesses_pre_option and monitor_option_accesses_legacy check this flag and bail out immediately if already processing, preventing re-entrant calls.

Why this works

The flag is set before add_option_usage() and cleared after. Any get_option() call that occurs during that window will see $is_processing = true and return immediately without entering the monitoring logic. This breaks the recursion chain while only skipping the nested call — the original monitoring call completes normally.

Test plan

  • Verify plugin activates without errors
  • Verify option tracking still works (options are recorded in the custom table)
  • Test with common object cache drop-ins (Redis, Memcached)
  • Test during plugin bulk updates (the scenario that triggered the original report)

🤖 Generated with Claude Code

Add a recursion guard ($is_processing flag) to both
monitor_option_accesses_pre_option and monitor_option_accesses_legacy
to prevent re-entrant calls from causing a stack overflow.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

Test on Playground
Test this pull request on the Playground
or download the zip

@ilicfilip ilicfilip requested review from aristath and jdevalk March 11, 2026 09:05
@ilicfilip ilicfilip merged commit b3eb545 into develop Mar 11, 2026
8 checks passed
@ilicfilip ilicfilip deleted the filip/fix-infinite-recursion branch March 11, 2026 09:29
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