feat(1314): add unclosed ob_start check#1318
Conversation
|
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 If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
2f01cac to
32eccbe
Compare
|
Thanks for working on this. The new check is a good direction, but I found a few issues that should be addressed before merge:
|
|
Could this be implemented as a PHPCS Sniff instead? Since it's walking over the tokens in the source file, this would seem more appropriate. |
- Move logic into PluginCheck.CodeAnalysis.UnclosedObStart sniff - Check wrapper now extends Abstract_PHP_CodeSniffer_Check - Handle fully-qualified \ob_start() calls (T_NAME_FULLY_QUALIFIED + T_NS_SEPARATOR) - Skip warning when ob_start() is followed by add_action/add_filter (hook paired buffer) - Point docs URL to WordPress 6.9 field guide - Add sniff unit test and FQN/hook-paired test fixtures Addresses review feedback from davidperezgar and westonruter on WordPress#1314.
|
Hi @davidperezgar @westonruter, thanks for the feedback. I reworked the check based on it. Main change: following @westonruter's suggestion, I moved the logic out of the custom The three issues from @davidperezgar:
Pairing logic: a close only pairs with a start when they share the same nesting context, so a buffer closed only inside an One thing I want your opinion on: the hook-paired heuristic is the softest part. I went with "next statement is If you'd rather keep the original |
|
@faisalahammad The check is missing Other Performance checks that also apply to the plugin directory follow the same pattern of returning both categories. For example, public function get_categories() {
return array(
Check_Categories::CATEGORY_PLUGIN_REPO,
Check_Categories::CATEGORY_PERFORMANCE,
);
}Please update |
….1.0 - Add CATEGORY_PLUGIN_REPO so check runs in Plugin Directory UI by default - Override add_result_message_for_file to use get_documentation_url() when no docs URL provided - Bump @SInCE tags from 1.4.0 to 2.1.0 in check and sniff - Add test_get_categories() asserting both categories Refs WordPress#1318
|
Hi @davidperezgar, pushed changes addressing your feedback:
Quality gates (PHPCS + PHPStan) pass clean. PR description updated to reflect the PHPCS sniff implementation. Ready for re-review when you have time. |
Is this accurate? |
Sorry, I unchecked the checkbox. Is there anything else I should fix before merging? Thank you. |
|
@faisalahammad then it seems you need to check this checkbox instead:
Right? If so then explain how the tools were used, per the description:
|
|
Done. Checked the AI-assisted box and described Claude Code's role: PHPCS sniff architecture research, scope-tracking logic, PHP 7.4/8.0 FQN handling, PHPUnit fixture scenarios, PR body draft. Could you please check again and share your feedback, @westonruter? |
- Reviewer requested $closure = static function() for optimization and memory safety - No $this used in closure body, so static is correct Refs WordPress#1318
|
@faisalahammad Thanks for working on the PR. Please resolve the merge conflicts and PR will be ready to ship. |
…nclosed-ob-start-check # Conflicts: # includes/Checker/Default_Check_Repository.php
|
Merge conflicts resolved. Merged trunk into this branch and fixed the one conflict in Tests pass after merge: PHPCS lint (0 errors), PHPStan (0 errors), PHPUnit (482 tests, 1407 assertions, all green). Branch is now mergeable with trunk. |
There was a problem hiding this comment.
Pull request overview
Adds a new static performance check to Plugin Check to detect ob_start() calls that are not reliably closed within the same logical scope, implemented via a dedicated PHPCS sniff and surfaced through the existing Abstract_PHP_CodeSniffer_Check infrastructure.
Changes:
- Introduces
PluginCheck.CodeAnalysis.UnclosedObStartPHPCS sniff to trackob_start()and matching close calls per scope (with a conservative hook-adjacent suppression heuristic). - Registers a new
unclosed_ob_startcheck so the sniff runs by default and is categorized under both Performance and Plugin Repo checks. - Adds PHPUnit coverage (plugin-check check runner) and PHPCS sniff unit tests with fixtures for paired/unpaired/conditional/FQN/hook-adjacent scenarios.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
includes/Checker/Checks/Performance/Unclosed_Ob_Start_Check.php |
New PHPCS-backed check wrapper exposing the sniff through Plugin Check. |
includes/Checker/Default_Check_Repository.php |
Registers the new unclosed_ob_start check in the default repository. |
phpcs-sniffs/PluginCheck/Sniffs/CodeAnalysis/UnclosedObStartSniff.php |
Implements the new sniff logic for detecting unclosed output buffers by scope. |
phpcs-sniffs/PluginCheck/ruleset.xml |
Enables the new sniff in the PluginCheck coding standard ruleset. |
phpcs-sniffs/PluginCheck/Tests/CodeAnalysis/UnclosedObStartUnitTest.php |
Adds PHPCS unit test expectations for the new sniff. |
phpcs-sniffs/PluginCheck/Tests/CodeAnalysis/UnclosedObStartUnitTest.inc |
Adds PHPCS fixture code covering the key detection scenarios. |
tests/phpunit/tests/Checker/Checks/Unclosed_Ob_Start_Check_Tests.php |
Adds PHPUnit test validating Plugin Check surfaces warnings from the sniff with correct locations. |
tests/phpunit/testdata/plugins/test-plugin-unclosed-ob-start/load.php |
Adds plugin fixture used by the PHPUnit test for real-world scenarios. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Add T_FN to get_scope_pointer() so ob_start() / closing calls inside arrow functions are correctly attributed to the arrow function scope - Add sniff unit test + PHPUnit integration test coverage for T_FN unpaired and paired scenarios - Replace brittle direct array-offset assertions with a assertWarningCodeOnLine() helper that uses reset() - Fill empty PR Screenshots table Addresses PR feedback from @copilot-pull-request-reviewer and @mukeshpanchal27. Refs WordPress#1318
|
@mukeshpanchal27 T_FN scope gap verified in commit a8a673b. Test cases added: Sniff unit test (
PHPUnit integration test (
Manual sniff run on the fixture produces 4 warnings on lines 14, 17, 29, 45 (T_FN included). Sniff unit test passes 16/16. |
- Clarify warning text for conditional-only closes (add reliably-executed) - Cache last registered token pointer in maybe_finalize() to avoid O(NxM) scan - Correct get_global_function_name() @return docstring (not lowercased) - Add unclosed_ob_start row to docs/checks.md Available Checks table Refs WordPress#1318
CI Fix — PHP (Sniffs) resolvedThe PHP (Sniffs) check failed on a self-lint of the new sniff file. Two
Whitespace only, no logic change. check-cs pass (exit 0) - sniff tests 16/16 - PHP 8.0 compatible. |
Replace verbose multi-sentence warning with concise two-sentence message per reviewer feedback. Refs WordPress#1318
What?
Closes #1314
Adds a new performance check to detect
ob_start()calls that are not paired with a corresponding buffer-closing function within the same scope. Prevents unpredictable behaviour caused by misaligned output buffers in shared WordPress environments.Why?
Every
ob_start()must be paired with a closing call (ob_get_clean(),ob_end_clean(),ob_get_flush(), orob_end_flush()) in the same scope. Unclosed buffers break headers, lose output, and cause redirect issues in shared hosting. WordPress core, themes, and other plugins all share the buffer stack.How?
Implemented as a PHPCS sniff (
PluginCheck.CodeAnalysis.UnclosedObStart) following @westonruter's suggestion:Abstract_PHP_CodeSniffer_Checklike other sniff-based checksconditionsdata for scope tracking instead of hand-rolled brace-depth\ob_start()calls (PHP 7.4 two-token form + PHP 8+T_NAME_FULLY_QUALIFIED)ob_end_clean()only inside anif) as warningsob_start()is immediately followed byadd_action()/add_filter()Default_Check_Repositoryunder bothCATEGORY_PERFORMANCEandCATEGORY_PLUGIN_REPOTesting Instructions
wp plugin check <plugin-slug>or use the Plugin Check admin UIob_start()— confirm a warning is reported on the exact lineob_start()/ob_end_clean()in the same function — confirm no warning\ob_start()in namespaced code — confirm detection worksob_start()+add_action()) — confirm no warningAI Usage Disclosure
If AI tools were used, please describe how they were used:
AbstractFunctionParameterSniff/AbstractFunctionRestrictionsSniffpatterns inphpcs-sniffs/PluginCheck/Sniffs/CodeAnalysis/)$tokens[ $stackPtr ]['conditions'])T_STRING+T_NS_SEPARATORvsT_NAME_FULLY_QUALIFIED)Screenshots or screencast