fix(aot): 修复动态调用中的方法重写处理问题

- 添加了对父类参数类型调用重写方法的测试用例
- 增加了方法重写检测逻辑,避免对重写方法进行去虚拟化
- 新增 getOverrideMethodName 方法统一获取重写方法名称
- 新增 isFinalClass 方法判断类是否为 final
- 修正了 canDevirtualize 方法中关于最终类的判断逻辑
- 统一使用 getOverrideMethodName 获取完整方法名
- 优化了 SSA 稳定对象
pull/3/head
韩天峰 2 months ago
parent 979ea385ca
commit 8148ffce1e
  1. 34
      src/Php/CompilerBase.php
  2. 4
      src/Php/Context/FunctionContext.php
  3. 37
      tests/aot/dynamic_call/call-parent-return-override.phpt

@ -4702,7 +4702,10 @@ class CompilerBase extends \PhpAot\Core\Translator
$methodIsAbstract = $class && $funcName && $this->hasClass($class) $methodIsAbstract = $class && $funcName && $this->hasClass($class)
&& ($this->getMethodFlags($class, $funcName) & Modifiers::ABSTRACT); && ($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); $methodPtr = $this->getMethodPtr($class, $funcName);
} else { } else {
$methodPtr = $method; $methodPtr = $method;
@ -5418,6 +5421,15 @@ class CompilerBase extends \PhpAot\Core\Translator
return isset($this->classMethodOverride[$fullMethodNameLower]) and $this->classMethodOverride[$fullMethodNameLower]; 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 protected function hasSubClasses(string $classNameLower): bool
{ {
return !empty($this->classSubClasses[$classNameLower]); return !empty($this->classSubClasses[$classNameLower]);
@ -5428,6 +5440,11 @@ class CompilerBase extends \PhpAot\Core\Translator
return $this->classDef && ($this->classDef->flags & Modifiers::FINAL) !== 0; 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 protected function getMethodFlags(string $class, string $method): int
{ {
if (!$this->hasClass($class)) { 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) * 2. $this->m() where m is final (can't be overridden)
* 3. $this->m() where m is private (not virtual) * 3. $this->m() where m is private (not virtual)
* 4. $obj->m() where obj's class has no known subclasses * 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 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 // Case 4: Typed object whose class has no known subclasses
if ($object !== 'this_' && $this->hasClass($class)) { if ($object !== 'this_' && $this->hasClass($class)) {
$classLower = strtolower($class); $classLower = strtolower($class);
if (!$this->hasSubClasses($classLower) && !$this->isInterface($class)) { if (!$this->hasSubClasses($classLower) && !$this->isInterface($class) && !$this->isAbstractClass($class)) {
return true; 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])) { if ($object !== 'this_' && isset($this->context->stableObjects[$object])) {
$stableClass = $this->context->stableObjects[$object]; $stableClass = $this->context->stableObjects[$object];
if ($this->hasClass($stableClass) && !$this->isAbstractClass($stableClass)) { if ($this->isFinalClass($stableClass)) {
return true; return true;
} }
} }
@ -5520,11 +5538,7 @@ class CompilerBase extends \PhpAot\Core\Translator
} }
} }
if ($classDef) { $fullMethodName = $this->getOverrideMethodName($class, $method);
$fullMethodName = $classDef->getNamespacedName(false) . '::' . $method;
} else {
$fullMethodName = $object . '::' . $method;
}
// 存在子类同名方法,尝试去虚化 // 存在子类同名方法,尝试去虚化
if ($this->isOverrideMethod($fullMethodName)) { if ($this->isOverrideMethod($fullMethodName)) {

@ -8,10 +8,12 @@
namespace PhpAot\Php\Context; namespace PhpAot\Php\Context;
use PhpAot\Php\Analysis\SsaBuilder;
class FunctionContext class FunctionContext
{ {
/** SSA builder for the current function. Built once per function, discarded with the context. */ /** 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). */ /** Map of SSA-stable object variable name => class name (SsaPropOptimizer). */
public array $stableObjects = []; public array $stableObjects = [];

@ -0,0 +1,37 @@
--TEST--
call overridden method through parent parameter type
--FILE--
<?php
use native_types;
class Base
{
public function run(): string
{
return 'base';
}
}
class Impl extends Base
{
public function run(): string
{
return 'impl';
}
}
function run(Base $obj): string
{
$alias = $obj;
return $alias->run();
}
function main(): int
{
$r = run(new Impl());
echo "result: $r\n";
return $r === 'impl' ? 0 : 1;
}
?>
--EXPECT--
result: impl
Loading…
Cancel
Save