From 8148ffce1e824735530bb5a364f905b61b2ab675 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Sat, 27 Jun 2026 09:08:02 +0800 Subject: [PATCH] =?UTF-8?q?fix(aot):=20=E4=BF=AE=E5=A4=8D=E5=8A=A8?= =?UTF-8?q?=E6=80=81=E8=B0=83=E7=94=A8=E4=B8=AD=E7=9A=84=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E9=87=8D=E5=86=99=E5=A4=84=E7=90=86=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加了对父类参数类型调用重写方法的测试用例 - 增加了方法重写检测逻辑,避免对重写方法进行去虚拟化 - 新增 getOverrideMethodName 方法统一获取重写方法名称 - 新增 isFinalClass 方法判断类是否为 final - 修正了 canDevirtualize 方法中关于最终类的判断逻辑 - 统一使用 getOverrideMethodName 获取完整方法名 - 优化了 SSA 稳定对象 --- src/Php/CompilerBase.php | 34 ++++++++++++----- src/Php/Context/FunctionContext.php | 4 +- .../call-parent-return-override.phpt | 37 +++++++++++++++++++ 3 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 tests/aot/dynamic_call/call-parent-return-override.phpt diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 724a1e52..c5884a2d 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -4702,7 +4702,10 @@ class CompilerBase extends \PhpAot\Core\Translator $methodIsAbstract = $class && $funcName && $this->hasClass($class) && ($this->getMethodFlags($class, $funcName) & Modifiers::ABSTRACT); - if ($class and $funcName and !$magicMethod and !$methodIsAbstract) { + $methodIsOverridden = $class && $funcName && $this->isOverrideMethod( + $this->getOverrideMethodName($class, $funcName) + ); + if ($class and $funcName and !$magicMethod and !$methodIsAbstract and !$methodIsOverridden) { $methodPtr = $this->getMethodPtr($class, $funcName); } else { $methodPtr = $method; @@ -5418,6 +5421,15 @@ class CompilerBase extends \PhpAot\Core\Translator return isset($this->classMethodOverride[$fullMethodNameLower]) and $this->classMethodOverride[$fullMethodNameLower]; } + protected function getOverrideMethodName(string $class, string $method): string + { + if (!$this->hasClass($class) && !$this->hasInterface($class) + && !$this->isInternalClass($class) && !$this->isInternalInterface($class)) { + $class = $this->getNamespacedClassName($class); + } + return $class . '::' . $method; + } + protected function hasSubClasses(string $classNameLower): bool { return !empty($this->classSubClasses[$classNameLower]); @@ -5428,6 +5440,11 @@ class CompilerBase extends \PhpAot\Core\Translator return $this->classDef && ($this->classDef->flags & Modifiers::FINAL) !== 0; } + protected function isFinalClass(string $class): bool + { + return $this->hasClass($class) && ($this->getClass($class)->flags & Modifiers::FINAL) !== 0; + } + protected function getMethodFlags(string $class, string $method): int { if (!$this->hasClass($class)) { @@ -5462,7 +5479,7 @@ class CompilerBase extends \PhpAot\Core\Translator * 2. $this->m() where m is final (can't be overridden) * 3. $this->m() where m is private (not virtual) * 4. $obj->m() where obj's class has no known subclasses - * 5. $obj->m() where obj is SSA-stable (single def, no escape) + * 5. $obj->m() where obj is SSA-stable and its class is final */ protected function canDevirtualize(string $object, string $class, string $method): bool { @@ -5480,15 +5497,16 @@ class CompilerBase extends \PhpAot\Core\Translator // Case 4: Typed object whose class has no known subclasses if ($object !== 'this_' && $this->hasClass($class)) { $classLower = strtolower($class); - if (!$this->hasSubClasses($classLower) && !$this->isInterface($class)) { + if (!$this->hasSubClasses($classLower) && !$this->isInterface($class) && !$this->isAbstractClass($class)) { return true; } } - // Case 5: SSA-stable object — compile-time proven exact type + // Case 5: SSA stability proves the variable identity, not necessarily + // the runtime class. Only final classes are exact enough here. if ($object !== 'this_' && isset($this->context->stableObjects[$object])) { $stableClass = $this->context->stableObjects[$object]; - if ($this->hasClass($stableClass) && !$this->isAbstractClass($stableClass)) { + if ($this->isFinalClass($stableClass)) { return true; } } @@ -5520,11 +5538,7 @@ class CompilerBase extends \PhpAot\Core\Translator } } - if ($classDef) { - $fullMethodName = $classDef->getNamespacedName(false) . '::' . $method; - } else { - $fullMethodName = $object . '::' . $method; - } + $fullMethodName = $this->getOverrideMethodName($class, $method); // 存在子类同名方法,尝试去虚化 if ($this->isOverrideMethod($fullMethodName)) { diff --git a/src/Php/Context/FunctionContext.php b/src/Php/Context/FunctionContext.php index 33eb40c2..e344d411 100644 --- a/src/Php/Context/FunctionContext.php +++ b/src/Php/Context/FunctionContext.php @@ -8,10 +8,12 @@ namespace PhpAot\Php\Context; +use PhpAot\Php\Analysis\SsaBuilder; + class FunctionContext { /** SSA builder for the current function. Built once per function, discarded with the context. */ - public ?\PhpAot\Php\Analysis\SsaBuilder $ssaBuilder = null; + public ?SsaBuilder $ssaBuilder = null; /** Map of SSA-stable object variable name => class name (SsaPropOptimizer). */ public array $stableObjects = []; diff --git a/tests/aot/dynamic_call/call-parent-return-override.phpt b/tests/aot/dynamic_call/call-parent-return-override.phpt new file mode 100644 index 00000000..d2a648eb --- /dev/null +++ b/tests/aot/dynamic_call/call-parent-return-override.phpt @@ -0,0 +1,37 @@ +--TEST-- +call overridden method through parent parameter type +--FILE-- +run(); +} + +function main(): int +{ + $r = run(new Impl()); + echo "result: $r\n"; + return $r === 'impl' ? 0 : 1; +} +?> +--EXPECT-- +result: impl