Exception handling does not work in standalone AOT binaries. Three separate failures, all reproduced on trivial programs.
1. Uncaught exception: silent abort
<?php
echo "BEFORE\n";
throw new LogicException("boom from user code");
Zend:
BEFORE
PHP Fatal error: Uncaught LogicException: boom from user code in /app/.../x1_uncaught.php:3
Stack trace:
#0 {main}
thrown in /app/.../x1_uncaught.php on line 3
AOT:
BEFORE
(rc=134, stdout ends, stderr EMPTY)
2. Caught exception still aborts, and getMessage() is empty
<?php
echo "BEFORE\n";
try { throw new LogicException("boom"); }
catch (LogicException $e) { echo "caught: ", $e->getMessage(), "\n"; }
echo "AFTER\n";
AOT: BEFORE
caught: <- catch block RAN, but getMessage() returned empty
(rc=134 — never reaches AFTER)
This is the significant one. The catch is entered, so dispatch partly works — but the message is lost and control never returns to normal flow. A program that correctly handles its own exception still dies.
3. Same for engine-named classes
throw new TypeError(...) behaves identically (rc=134, silent), so it is not specific to LogicException.
Not the #579 stub class
Rebuilt with PHP_COMPILER_WARN_EXTERNAL_STUBS=1 (#23609): no warning fires, so no external-method stub is reached on this path. That rules out the mechanism behind #23540 and points at the throw/catch machinery itself.
Printers exist for engine-raised errors (TypeErrorRaise emits PHP Fatal error: Uncaught TypeError: %s, likewise ValueError/ArgumentCountError, and ErrorRaise for Error), but a user throw routes through JitThrow, which has no uncaught printer in its emitted IR — its only "Uncaught" string is a PHP-side throw new \Exception('Uncaught exception in JIT'), not something the binary can print.
Why this is release-blocking
Exceptions are how PHP reports errors. Any non-trivial program uses them, and today in AOT: the message is lost, the fatal is invisible, and even correct try/catch code dies. It also made #23540 far harder to find than it needed to be — the LogicException thrown by VmActiveContextJitHelper carried an exact description of the problem that never reached the user.
Done when: the uncaught case prints a Zend-shaped PHP Fatal error: Uncaught <Class>: <message> with file and line; the caught case prints the message and continues to AFTER; and script/differential-sweep.sh --aot carries both shapes.
Exception handling does not work in standalone AOT binaries. Three separate failures, all reproduced on trivial programs.
1. Uncaught exception: silent abort
2. Caught exception still aborts, and getMessage() is empty
This is the significant one. The
catchis entered, so dispatch partly works — but the message is lost and control never returns to normal flow. A program that correctly handles its own exception still dies.3. Same for engine-named classes
throw new TypeError(...)behaves identically (rc=134, silent), so it is not specific toLogicException.Not the #579 stub class
Rebuilt with
PHP_COMPILER_WARN_EXTERNAL_STUBS=1(#23609): no warning fires, so no external-method stub is reached on this path. That rules out the mechanism behind #23540 and points at the throw/catch machinery itself.Printers exist for engine-raised errors (
TypeErrorRaiseemitsPHP Fatal error: Uncaught TypeError: %s, likewise ValueError/ArgumentCountError, andErrorRaisefor Error), but a userthrowroutes throughJitThrow, which has no uncaught printer in its emitted IR — its only "Uncaught" string is a PHP-sidethrow new \Exception('Uncaught exception in JIT'), not something the binary can print.Why this is release-blocking
Exceptions are how PHP reports errors. Any non-trivial program uses them, and today in AOT: the message is lost, the fatal is invisible, and even correct
try/catchcode dies. It also made #23540 far harder to find than it needed to be — theLogicExceptionthrown byVmActiveContextJitHelpercarried an exact description of the problem that never reached the user.Done when: the uncaught case prints a Zend-shaped
PHP Fatal error: Uncaught <Class>: <message>with file and line; the caught case prints the message and continues toAFTER; andscript/differential-sweep.sh --aotcarries both shapes.