Skip to content

feat(checks): detect production-time PHP error reporting changes#1317

Open
faisalahammad wants to merge 3 commits into
WordPress:trunkfrom
faisalahammad:enhancement/1315-php-error-reporting-check
Open

feat(checks): detect production-time PHP error reporting changes#1317
faisalahammad wants to merge 3 commits into
WordPress:trunkfrom
faisalahammad:enhancement/1315-php-error-reporting-check

Conversation

@faisalahammad

@faisalahammad faisalahammad commented May 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a new static check Php_Error_Reporting_Check that flags plugins which modify PHP error-reporting configurations or redefine core WordPress debug constants in production. This prevents sensitive information disclosure and ensures a standard debugging experience on WordPress sites.

Fixes #1315

Changes

Php_Error_Reporting_Check

Before:
No check existed to flag PHP error reporting changes.

After:

class Php_Error_Reporting_Check extends Abstract_File_Check {
	// Trait and Category declarations...

	protected function check_files( Check_Result $result, array $files ) {
		$php_files   = self::filter_files_by_extension( $files, 'php' );
		$plugin_path = $result->plugin()->path();

		foreach ( $php_files as $file ) {
			// Skip test suite folders or files relative to the plugin\'s root path.
			$relative_file = str_replace( $plugin_path, '', $file );
			if ( preg_match( \'#^(?:tests|test|testdata|phpunit)/#i\', $relative_file ) || preg_match( \'#/phpunit[^/]*$\#i\', $relative_file ) ) {
				continue;
			}

			$this->check_file( $result, $file );
		}
	}
}

Why: Leverages an AST-based search (using PhpParser\\ParserFactory) to precisely flag direct error_reporting() calls, ini_set()/ini_alter() calls with error_reporting/display_errors, and the definition of core debug constants (WP_DEBUG, etc.). The check has a robust fallback to regex search if AST parsing fails.

Default_Check_Repository

Before:

				\'i18n_usage\'                 => new Checks\\General\\I18n_Usage_Check(),
				\'enqueued_scripts_size\'      => new Checks\\Performance\\Enqueued_Scripts_Size_Check(),

After:

				\'i18n_usage\'                 => new Checks\\General\\I18n_Usage_Check(),
				\'php_error_reporting\'        => new Checks\\General\\Php_Error_Reporting_Check(),
				\'enqueued_scripts_size\'      => new Checks\\Performance\\Enqueued_Scripts_Size_Check(),

Why: Registers the new static check class as a default check in the General category.

Testing

Test 1: Run the automated unit test suite

  1. Run PHPUnit on the new test class:
vendor/bin/phpunit tests/phpunit/tests/Checker/Checks/Php_Error_Reporting_Check_Tests.php

Result: Works as expected (2 tests, 6 assertions successfully pass).

Test 2: Perform static analysis and lint checks

  1. Execute composer run-script lint
  2. Execute composer run-script phpstan
    Result: Works as expected (0 errors, completely clean).
Open WordPress Playground Preview

- Add Php_Error_Reporting_Check to detect error_reporting(), ini_set('display_errors'), and redefine of debug constants (WP_DEBUG, etc.).
- Register Php_Error_Reporting_Check in Default_Check_Repository.
- Add unit tests and test plugins fixtures for verification.

Fixes WordPress#1315
@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.

Add @SuppressWarnings(PHPMD.NPathComplexity) to check_ast() method.
NPath 1206 exceeds threshold 200; method is linear AST scan, not truly complex.
Same annotation pattern used in Enqueued_Scripts_Size_Check and Direct_File_Access_Check.

Refs WordPress#1317
@davidperezgar

Copy link
Copy Markdown
Member

Faisal, Thanks for the PR. The implemented scope looks aligned with Internal Scanner’s calls-php_error rule: it checks error_reporting(), ini_set()/ini_alter() for error_reporting and display_errors, and debug constant definitions for WP_DEBUG, WP_DEBUG_LOG, SCRIPT_DEBUG, and WP_DEBUG_DISPLAY.

A couple of things need tightening before this is ready:

  • The tests don’t yet prove the full acceptance criteria. The fixture covers ini_set( 'display_errors', ... ) and ini_alter( 'error_reporting', ... ), but not ini_set( 'error_reporting', ... ) or ini_alter( 'display_errors', ... ). Also, the assertions only check the broad sniff codes, so the test could still pass if one of those specific patterns stopped being detected.

Could you please add explicit coverage for all requested patterns, ideally including a dedicated PHPCS sniff unit test like the existing sniffs have, and fix the coding-standard issues? Once that’s done, this should be much closer.

Extract detection logic from Php_Error_Reporting_Check into a new
PhpErrorReportingSniff (PluginCheck.CodeAnalysis.PhpErrorReporting),
registered in the existing PluginCheck standard. The check now
extends Abstract_PHP_CodeSniffer_Check and delegates to the sniff,
matching the project's existing sniff-driven check pattern.

Sniff improvements over the previous AST implementation:
- First-argument detection uses PHPCSUtils\Utils\PassedParameters,
  eliminating false positives for calls like
  `ini_set( some_function(), 'error_reporting' )`.
- Multi-const declarations (`const A = 1, WP_DEBUG = true;`) are
  fully inspected and report at the offending constant, not the
  `const` keyword.
- The error message for `const` declarations no longer reads
  `const()`.
- Argument case is normalized to lowercase for stable error codes.

Test improvements addressing reviewer feedback:
- New PhpErrorReportingUnitTest with per-line assertions for all
  11 patterns the sniff targets, plus a negative case and a
  multi-const fixture entry.
- The PHPUnit check test now asserts every expected line (12, 14,
  16, 18, 20, 22, 24, 26, 28, 30, 32) carries the
  `php_error_reporting_detected` code, instead of relying on a
  loose `get_warning_count() === 8` count.
- Fixture now covers `ini_alter( 'error_reporting', ... )`,
  `define( 'WP_DEBUG_DISPLAY', ... )`, and `const` declarations,
  the three patterns the reviewer flagged as missing.
- Removed the obsolete tests/some-test.php file; the framework's
  --ignore patterns handle test-directory exclusion now.

All checks green:
- composer run lint / phpstan / phpmd: clean
- composer run run-tests (sniffs): 13/13
- npm run test-php (full suite): 474/474, 1364 assertions

Refs WordPress#1315
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@faisalahammad faisalahammad left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review feedback addressed

Thanks for the detailed feedback on this PR. All blocker items + the recommended improvement are now addressed. Summary of the changes (commit a9fef37b):

Refactor — extracted a dedicated PHPCS sniff

Moved the detection logic out of the bespoke Abstract_File_Check + nikic/php-parser AST walker into a new PhpErrorReportingSniff, registered in the existing PluginCheck standard and consumed by the check via the standard Abstract_PHP_CodeSniffer_Check pattern (get_args() returns the standard + sniff code). This matches the existing pattern used by Nonce_Verification_CheckVerifyNonceSniff, so we now have one source of truth for detection and can reuse PHPCS's per-line assertion tooling.

Test coverage — granular per-line assertions

  • New sniff unit test PhpErrorReportingUnitTest + .inc fixture with explicit per-line getErrorList() for all 11 patterns, plus a negative case (ini_set( some_function(), 'error_reporting' )) and a multi-const declaration.
  • The PHPUnit Php_Error_Reporting_Check_Tests::test_run_with_errors now asserts every line (12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32) carries the php_error_reporting_detected code, instead of relying on get_warning_count() === 8.
  • Fixture now covers ini_alter( 'error_reporting', ... ), define( 'WP_DEBUG_DISPLAY', ... ), and const declarations — the three patterns the review flagged as missing.
  • The test that exercised the old AST's "skip tests directories" behaviour is gone (the framework's --ignore patterns handle that now).

CodeRabbit findings addressed

  • First-argument detection now uses PHPCSUtils\Utils\PassedParameters — no false positive for ini_set( some_function(), 'error_reporting' ).
  • Multi-const declarations are fully inspected; reports point at the offending constant, not the const keyword.
  • Error message for const declarations no longer says const().
  • Argument case is normalized to lowercase for stable error codes.

Coding standards

  • composer run lint (phpcs): clean
  • composer run phpstan: clean
  • composer run phpmd: clean
  • composer run run-tests (sniff suite): 13/13 green
  • npm run test-php (full suite): 474/474, 1364 assertions

Unchanged

  • Check slug (php_error_reporting), category (General), severity, documentation URL, and the user-facing warning text.

Let me know if anything still looks off and I'll iterate.

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 new check for production-time PHP error reporting changes

2 participants