From 057c21780034ca824e573ef943607111318571bf Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Sun, 30 Aug 2026 13:06:16 +0800 Subject: [PATCH] fix(optimizer): preserve class_exists autoload evaluation --- .../code/class-exists-autoload-argument.php | 21 ++++++++++++++ phpunit/src/ClassExistsTraitFoldTest.php | 8 +++++ src/Optimizer/FuncCallOptimizer.php | 6 ++++ tests/compiler/stdlib/class_exists.phpt | 29 +++++++++++++++++++ 4 files changed, 64 insertions(+) create mode 100644 phpunit/code/class-exists-autoload-argument.php diff --git a/phpunit/code/class-exists-autoload-argument.php b/phpunit/code/class-exists-autoload-argument.php new file mode 100644 index 00000000..ee818982 --- /dev/null +++ b/phpunit/code/class-exists-autoload-argument.php @@ -0,0 +1,21 @@ +compileToCpp('class-exists-autoload-argument.php'); + + self::assertStringContainsString('php::fn::class_exists(', $cpp); + self::assertStringContainsString('php_autoloadflag()', $cpp); + } + private function compileToCpp(string $file): string { global $translator; diff --git a/src/Optimizer/FuncCallOptimizer.php b/src/Optimizer/FuncCallOptimizer.php index 83573bb1..e2345845 100644 --- a/src/Optimizer/FuncCallOptimizer.php +++ b/src/Optimizer/FuncCallOptimizer.php @@ -663,6 +663,12 @@ trait FuncCallOptimizer protected function doFoldKnownClass(Node\Expr\FuncCall $expr): string|false { + // An explicit $autoload argument must still be evaluated, including + // any side effects or exception it produces. Leave that form on the + // normal call path instead of duplicating argument semantics here. + if (count($expr->args) !== 1 || !($expr->args[0] instanceof Node\Arg)) { + return false; + } $cn = $expr->args[0]->value; if (!$this->isScalarString($cn) || !$this->hasClass($cn->value)) { return false; diff --git a/tests/compiler/stdlib/class_exists.phpt b/tests/compiler/stdlib/class_exists.phpt index 039466d1..7a34f04e 100644 --- a/tests/compiler/stdlib/class_exists.phpt +++ b/tests/compiler/stdlib/class_exists.phpt @@ -7,6 +7,18 @@ interface MyInterface {} trait MyTrait {} enum MyEnum { case Foo; } +function autoloadFlag(string $label): bool +{ + echo "autoload-$label\n"; + return false; +} + +function throwingAutoloadFlag(): bool +{ + echo "autoload-throw\n"; + throw new RuntimeException('autoload-argument'); +} + function main() { define("MY_CONST", 42); @@ -22,6 +34,17 @@ function main() { $traitName = "MyTrait"; var_dump(class_exists($traitName)); + // An explicit autoload expression must be evaluated exactly once even + // when the literal name makes the final answer statically known. + var_dump(class_exists("MyClass", autoloadFlag('class'))); + var_dump(class_exists("MyTrait", autoloadFlag('trait'))); + try { + class_exists("MyClass", throwingAutoloadFlag()); + echo "exception-not-thrown\n"; + } catch (RuntimeException $e) { + echo "caught=", $e->getMessage(), "\n"; + } + // interface_exists var_dump(interface_exists("MyInterface")); var_dump(interface_exists("NonexistentInterface")); @@ -46,6 +69,12 @@ bool(false) bool(false) bool(false) bool(false) +autoload-class +bool(true) +autoload-trait +bool(false) +autoload-throw +caught=autoload-argument bool(true) bool(false) bool(false)