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