Skip to content

feat(checker): add runtime fatal-error prevention check#1320

Open
faisalahammad wants to merge 2 commits into
WordPress:trunkfrom
faisalahammad:fix/1273-runtime-fatal-error-prevention
Open

feat(checker): add runtime fatal-error prevention check#1320
faisalahammad wants to merge 2 commits into
WordPress:trunkfrom
faisalahammad:fix/1273-runtime-fatal-error-prevention

Conversation

@faisalahammad

@faisalahammad faisalahammad commented May 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a new static check Runtime_Fatal_Error_Prevention_Check and corresponding PHPCS sniff RuntimeFatalErrorPreventionSniff to analyze plugins for high-risk coding patterns that commonly trigger runtime fatal errors. These include unguarded imports, direct/unguarded class instantiations or function calls of optional components, dynamic callback definitions that might be missing, and incorrect class method hook attachments.

Fixes #1273

Changes

phpcs-rulesets/plugin-check.ruleset.xml

Before:

	

After:

	
	<!-- Prevent runtime fatal errors. -->
	<rule ref="PluginCheck.CodeAnalysis.RuntimeFatalErrorPrevention">
		<type>warning</type>
	</rule>

Why: Registers the new custom PHPCS sniff under the core plugin-check ruleset to run as part of the PHPCS static checks. Findings are emitted as warnings (not errors) to avoid blocking plugin submission on patterns that may be legitimate for plugins with custom integration loaders.

includes/Checker/Default_Check_Repository.php

Before:

		'wp_functions_compatibility' => new Checks\Plugin_Repo\WP_Functions_Compatibility_Check(),
		)
	);

After:

		'wp_functions_compatibility'     => new Checks\Plugin_Repo\WP_Functions_Compatibility_Check(),
		'runtime_fatal_error_prevention' => new Checks\Plugin_Repo\Runtime_Fatal_Error_Prevention_Check(),
		)
	);

Why: Registers the new Runtime_Fatal_Error_Prevention_Check static check class into the default check repository so it gets run automatically during plugin checks.

Filter hooks for third-party lists

The sniff exposes two filter hooks so plugin authors can extend or override the default lists of optional integration functions and classes:

add_filter( 'wp_plugin_check_runtime_fatal_error_integration_functions', function( $defaults ) {
    return array_merge( $defaults, array( 'my_plugin_helper' ) );
} );

add_filter( 'wp_plugin_check_runtime_fatal_error_optional_classes', function( $defaults ) {
    return array_merge( $defaults, array( 'My_Plugin\\Integration' ) );
} );

Returning an empty array disables all third-party-name-based checks.

Testing

Test 1: Run Sniff Unit Tests

  1. Run PHPCS unit tests on the custom sniff:
    vendor/bin/run-phpcs-tests phpcs-sniffs/PluginCheck/Tests/CodeAnalysis/RuntimeFatalErrorPreventionUnitTest.php

Result: Works as expected (All tests pass)

Test 2: Run Plugin Check PHPUnit Integration Tests

  1. Start the test stack: npm run wp-env:start:tests
  2. Run checker unit tests:
    npm run test-php -- --filter=Runtime_Fatal_Error_Prevention_Check_Tests

Result: Works as expected (All tests pass)

Test 3: Manual PHPCS run on fixture

  1. Run PHPCS on the with-errors fixture plugin:
    vendor/bin/phpcs --standard=phpcs-rulesets/plugin-check.ruleset.xml --sniffs=PluginCheck.CodeAnalysis.RuntimeFatalErrorPrevention tests/phpunit/testdata/plugins/test-plugin-runtime-fatal-error-prevention-with-errors/load.php

Result: 0 errors, 7 warnings at the expected lines (18, 20, 22, 24, 26, 30, 36).

- Implement RuntimeFatalErrorPreventionSniff to parse and analyze common runtime fatal patterns.
- Register check in Default_Check_Repository and ruleset.
- Add comprehensive PHPUnit and sniff unit tests.

Fixes WordPress#1273
@github-actions

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: faisalahammad <faisalahammad@git.wordpress.org>
Co-authored-by: davidperezgar <davidperez@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@davidperezgar

Copy link
Copy Markdown
Member

The function-call detection seems aligned with the issue scope, since #1273 explicitly includes “calling plugin/theme integration functions without function_exists guard” as one of the runtime fatal patterns.

That said, I think this part may need more caution before being added as a stable/default check. A hardcoded list of third-party functions/classes could produce false positives for plugins that declare dependencies or load integrations in a way this sniff cannot understand. Should this initially be experimental, reported as warnings, or made dependency-aware/configurable?

Also, the nearby goto rule in the ruleset is pre-existing context, not part of this PR’s functional scope.

@faisalahammad

Copy link
Copy Markdown
Contributor Author

Thanks for the caution — agreed a hardcoded third-party list could false-positive on plugins that load integrations the sniff cannot statically see. Applied two changes:

1. All findings are now warnings, not errors. Converted every addError() to addWarning() in the sniff (and added <type>warning</type> in phpcs-rulesets/plugin-check.ruleset.xml as defense in depth, matching how NonceVerification / ValidatedSanitizedInput / DevelopmentFunctions are handled). Findings still surface in the report but no longer block plugin submission.

2. Lists are now filterable. Two new filter hooks let plugin authors extend or override the defaults:

add_filter( 'wp_plugin_check_runtime_fatal_error_integration_functions', function( $defaults ) {
    return array_merge( $defaults, array( 'my_plugin_helper' ) );
} );

add_filter( 'wp_plugin_check_runtime_fatal_error_optional_classes', function( $defaults ) {
    return array_merge( $defaults, array( 'My_Plugin\\Integration' ) );
} );

The filter also short-circuits cleanly: returning an empty array disables all third-party-name-based checks if a plugin author wants that.

Kept Stable_Check (not Experimental_Check) because no Plugin_Repo check currently uses experimental, and hiding the check from default runs would defeat the value. Open to switching to experimental if maintainers prefer that route — happy to update.

Happy to refine filter names, expand defaults, or switch to experimental — let me know.

…able

- Convert all addError() to addWarning() in RuntimeFatalErrorPreventionSniff
- Add <type>warning</type> override in plugin-check.ruleset.xml
- Expose integration_functions and optional_classes as filterable lists:
  - wp_plugin_check_runtime_fatal_error_integration_functions
  - wp_plugin_check_runtime_fatal_error_optional_classes
- Update sniff unit test + integration test to assert warnings, not errors
- Add function_exists() guard around apply_filters() so sniff runs under
  both PHPCS and Plugin Check runner

Addresses davidperezgar review feedback on WordPress#1320: hardcoded third-party
lists could false-positive on plugins that load integrations the sniff
cannot statically see. Findings now require review, do not block
submission, and plugin authors can extend or override the defaults.

Refs WordPress#1320
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.

Add check: runtime fatal-error prevention in PHP plugin code

2 participants