Skip to content

Fix GH-24631: Report delayed OOM when compilation of the main script fails - #24632

Open
lazerg wants to merge 7 commits into
php:PHP-8.4from
lazerg:fix/gh-24631-delayed-oom-compile-fail
Open

lazerg wants to merge 7 commits into
php:PHP-8.4from
lazerg:fix/gh-24631-delayed-oom-compile-fail

Conversation

@lazerg

@lazerg lazerg commented Oct 24, 2026

Copy link
Copy Markdown
Contributor

zend_execute_script() only handled a pending delayed OOM when compilation produced an op_array, so a script that hit memory_limit while compiling and then failed with a parse error reported only the parse error. The OOM is now handled in the failure path too, dropping the pending ParseError so the memory error is the one reported.

Fixes GH-24631

@pentolope

Copy link
Copy Markdown

@lazerg I'd check that sapi/fuzzer/fuzzer-sapi.c is or gets fixed as well; it is the same pattern.

@lazerg

lazerg commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

Good catch, fuzzer-sapi.c had the same gap: the delayed OOM was only handled when compilation succeeded. I added the same handling for the failed-compile case in e305af1.

@ndossche ndossche left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is slightly incomplete as zend_eval_stringl and php_lint_script need handling too.

Comment thread sapi/fuzzer/fuzzer-sapi.c Outdated
zend_destroy_static_vars(op_array);
destroy_op_array(op_array);
efree(op_array);
} else if (ZEND_HAS_OOM()) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The zend_clear_exception call is necessary, but depends on the assumption that extensions handle clearing properly. As we now know, fiber and spl did not. The fix for fiber is underway, for spl there's an open PR.
Checking for ZEND_OOM_STATE_PENDING rather than ZEND_HAS_OOM is probably better as a defensive measure (same below)

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.

I switched all the new checks to ZEND_OOM_STATE_PENDING in 631a3b8, so an OOM that was already handled no longer clears an unrelated exception.

@lazerg
lazerg requested a review from bukka as a code owner October 24, 2026 10:47
@lazerg

lazerg commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

@ndossche I added the same handling to zend_eval_stringl() and php_lint_script() in 631a3b8, with tests for php -r and php -l. php -l also lost the OOM when the file compiled without errors, so the lint check covers that case too.

@ndossche ndossche left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please see my tiny remark and please drop the NEWS change so CI can run

Comment thread main/main.c
retval = SUCCESS;
}
if (EG(delayed_oom).state == ZEND_OOM_STATE_PENDING) {
retval = FAILURE;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You don't need this line

@lazerg lazerg Oct 25, 2026 •

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.

It is needed: zend_handle_delayed_oom() bails out here, so without it php -l on a valid file prints "No syntax errors detected" after the fatal error and oom_compile_failure_lint.phpt fails. I dropped the NEWS change.

@anun333

anun333 commented Oct 25, 2026

Copy link
Copy Markdown

Quoted below: written with Claude, an LLM, as CONTRIBUTING.md asks us to mark. The builds and test runs behind it are on my machine; the commands to repeat them are under the fold.

Tested at 4f479a84d85 on debug builds:

Test PHP-8.4 without (51808ac5478) this PR this PR applied onto master (93e09753772)
oom_compile_failure.phpt FAIL PASS FAIL: only an extra Stack trace: / #0 {main}
oom_compile_failure_eval_string.phpt FAIL PASS the same
oom_compile_failure_lint.phpt FAIL PASS the same
gh24631.phpt from #24694 FAIL PASS

The fix itself carries over to master; the three tests fail there only because fatal_error_backtraces (PHP 8.5+, on by default) adds a backtrace to the fatal. Adding -d fatal_error_backtraces=0 to the child command line in each test makes all three pass on master and still pass on PHP-8.4, where the setting is unknown and ignored:

-echo shell_exec(escapeshellarg($php) . ' -n -d memory_limit=6M ' . escapeshellarg($fname) . ' 2>&1');
+echo shell_exec(escapeshellarg($php) . ' -n -d fatal_error_backtraces=0 -d memory_limit=6M ' . escapeshellarg($fname) . ' 2>&1');

(and likewise in the eval_string and lint tests). That saves the merge-up from editing them.

How this was tested

Builds (Linux x86_64, gcc 13.3), each tree the same way:

./buildconf --force
./configure --enable-debug --with-mysqli --disable-cgi --disable-phpdbg --disable-fileinfo
make -j4

The master tree: git checkout origin/master && git cherry-pick $(git merge-base origin/PHP-8.4 pull/24632/head)..pull/24632/head (NEWS conflicts only). Each test: TEST_PHP_EXECUTABLE=sapi/cli/php sapi/cli/php run-tests.php -q -n <test>; with the one-line change above, each passes on both trees.

@lazerg

lazerg commented Oct 26, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, I added -d fatal_error_backtraces=0 to the three tests in a2d33cc so they also pass after the merge-up to master.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants