Skip to content

AOT: exceptions are broken — caught blocks abort, getMessage() empty, uncaught prints nothing #23641

Description

@PurHur

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.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:compilerCompiler / CFG / JITbugSomething isn't workingimplementation-readySpec complete: repro, php-src ref, done-when — safe for workers to claimphase-3:aotPhase 3 – AOT deploymentrelease-blockerBlocks tagged user release

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions