feat(compiler): 实现对 static 关键字的支持

- 修改 getNativeStaticMethod 方法为 getNativeMethod,增加继承链方法查找逻辑
- 添加对 static::class 和 self::class 的差异化处理
- 实现静态编译时对 static 调用的正确解析和转换
- 增加对 late static binding 的支持
- 添加相关辅助函数 php_get_called_class 和 php_get_called_ce
- 更新测试用例验证 static 关键字功能正确性
pull/1/head
韩天峰 6 months ago
parent 4f375bf869
commit d0d66a0982
  1. 39
      examples/callstack.php
  2. 96
      src/Php/CompilerBase.php
  3. 20
      src/cpp/main.cc
  4. 4
      src/cpp/php_aot_helper.h
  5. 2
      src/template/extension.cc.php
  6. 40
      tests/aot/class-static-002.phpt

@ -0,0 +1,39 @@
<?php
class A
{
static function foo()
{
echo "foo 中的 static::class = " . static::class . "\n";
}
static function bar()
{
// echo "bar 中的 self::class = " . self::class . "\n";
self::foo(); // 这里使用 self 调用
}
static function baz()
{
echo "baz 中的 self::class = " . self::class . "\n";
}
}
class B extends A
{
// 没有重写任何方法
}
function main()
{
// echo "=== 调用 A::bar() ===\n";
// A::bar();
echo "\n=== 调用 B::bar() ===\n";
B::bar();
// echo "\n=== 调用 A::baz() 和 B::baz() ===\n";
// A::baz();
// B::baz();;
}

@ -1480,16 +1480,36 @@ class CompilerBase extends \PhpAot\Core\Translator
return array_key_exists($this->escapeFunction($name), $this->nativeFunctions); return array_key_exists($this->escapeFunction($name), $this->nativeFunctions);
} }
protected function getNativeStaticMethod(string $class, string $method): string|false protected function getNativeMethod(Node\Expr\MethodCall|Node\Expr\StaticCall $expr, string $class, string $method): string|false
{ {
if (!$this->hasNativeClass($class)) { if (!$this->hasNativeClass($class)) {
return false; return false;
} }
$class = $this->getClassDef($class);
if (!$class->hasMethod($method)) { $classDef = $this->getClassDef($class);
return false; $methodDef = null;
// 递归查找,若子类中未定义方法,则尝试查找父类是否存在此方法
while ($classDef) {
if (!$classDef->hasMethod($method)) {
if (!$classDef->extends) {
return false;
}
$classDef = $this->getClassDef($classDef->extends);
} else {
$methodDef = $classDef->methods[$method];
break;
}
}
if (!$this->checkAccessible($classDef, $methodDef)) {
$this->fatalError($expr, 'Method `' . $classDef->getNamespacedName() . '::' . $method . '()` is not accessible');
}
if (count($expr->args) < $methodDef->functionDef->argCountRequired) {
$this->fatalError($expr, 'Method `' . $classDef->getNamespacedName() . '::' . $method . '()` requires ' . $methodDef->functionDef->argCountRequired . ' arguments, ' . count($expr->args) . ' given');
} elseif (count($expr->args) > count($methodDef->functionDef->argInfoList)) {
$this->fatalError($expr, 'Method `' . $classDef->getNamespacedName() . '::' . $method . '()` accepts ' . count($methodDef->functionDef->argInfoList) . ' arguments, ' . count($expr->args) . ' given');
} }
return $this->getNativeName($method, $class->namespace, $class->name); return $this->getNativeName($method, $classDef->namespace, $classDef->name);
} }
protected function getClassDef(string $name): ClassDef protected function getClassDef(string $name): ClassDef
@ -3391,8 +3411,10 @@ class CompilerBase extends \PhpAot\Core\Translator
* 对 static 的支持存在问题,静态编译时无法获得实际运行时的子类名,所以只能使用 self * 对 static 的支持存在问题,静态编译时无法获得实际运行时的子类名,所以只能使用 self
* self 是在编译期确定的,而 static 是运行时确定的,但使用 AOT 编译为可执行文件后,运行时类的名称是无法确定的 * self 是在编译期确定的,而 static 是运行时确定的,但使用 AOT 编译为可执行文件后,运行时类的名称是无法确定的
*/ */
if ($id === 'self' or $id === 'static') { if ($id === 'self') {
$id = $this->getNamespacedClassName($this->class); $id = $this->getNamespacedClassName($this->class);
} elseif ($id === 'static') {
return 'php_get_called_class(this_)';
} }
if ($this->isNameExpr($node) or $this->isIdExpr($node)) { if ($this->isNameExpr($node) or $this->isIdExpr($node)) {
return $literal ? $this->getLiteralString($id) : $this->genCharPtr($id, true); return $literal ? $this->getLiteralString($id) : $this->genCharPtr($id, true);
@ -3409,8 +3431,8 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseStaticCall(Node\Expr\StaticCall $expr): string protected function parseStaticCall(Node\Expr\StaticCall $expr): string
{ {
$placeHolder = '';
$self = false; $self = false;
$callScope = [];
if ($this->isVarExpr($expr->class) or $this->isVarExpr($expr->name)) { if ($this->isVarExpr($expr->class) or $this->isVarExpr($expr->name)) {
$var = $this->parseIdentifier($expr->class); $var = $this->parseIdentifier($expr->class);
if ($this->isTypedObject($var)) { if ($this->isTypedObject($var)) {
@ -3438,17 +3460,24 @@ class CompilerBase extends \PhpAot\Core\Translator
$this->beforeStmtLines[] = '// Static Method Call: ' . $class . '::' . $method . '()'; $this->beforeStmtLines[] = '// Static Method Call: ' . $class . '::' . $method . '()';
if ($this->isNameExpr($expr->class) and $this->isIdExpr($expr->name)) { if ($this->isNameExpr($expr->class) and $this->isIdExpr($expr->name)) {
$nativeFunc = $this->getNativeStaticMethod($class, $method); $callScope = [$this->genCharPtr($class, true), $this->genCharPtr($method)];
}
if ($callScope) {
$nativeFunc = $this->getNativeMethod($expr, $class, $method);
if ($nativeFunc) { if ($nativeFunc) {
try { try {
$args = $this->parseNativeCallArgs($expr->args, $nativeFunc); $args = $this->parseNativeCallArgs($expr->args, $nativeFunc);
} catch (PlaceHolder) { } catch (PlaceHolder) {
return $this->genPlaceHolder($this->genArray([$this->genCharPtr($class, true), $this->genCharPtr($method)])); return $this->genPlaceHolder($this->genArray($callScope));
} }
$object = 'php::null_object';
// 在方法定义中使用了当前类的方法 self::method(),依然应该传递 this_ 指针 // 在方法定义中使用了当前类的方法 self::method(),依然应该传递 this_ 指针
if ($this->methodDef and $self) { if ($this->methodDef and $self) {
$object = 'this_'; $object = 'this_';
} else {
$object = $this->genTmpVarName();
$this->addLocalVar($object, self::TYPE_OBJECT);
$this->beforeStmtLines[] = 'Z_PTR_P('.$object.'.ptr()) = ' . $this->getClassEntryPtr($class) . ';';
} }
if ($args) { if ($args) {
return self::PREFIX . $nativeFunc . '(' . $object . ', ' . $args . ')'; return self::PREFIX . $nativeFunc . '(' . $object . ', ' . $args . ')';
@ -3459,14 +3488,15 @@ class CompilerBase extends \PhpAot\Core\Translator
} }
$ce = $this->getClassEntryPtr($class); $ce = $this->getClassEntryPtr($class);
$fn = $ce . ', ' . $this->getFuncPtr($class . '::' . $method, false); $fn = $ce . ', ' . $this->getFuncPtr($class . '::' . $method, false);
$placeHolder = $this->genArray([$this->genCharPtr($class, true), $this->genCharPtr($method)]); $placeHolder = $this->genArray($callScope);
} }
$call = 'php::call'; $call = 'php::call';
if (empty($expr->args)) { if (empty($expr->args)) {
return $call . '(' . $fn . ')'; return $call . '(' . $fn . ')';
} }
try { try {
return $call . '(' . $fn . ', ' . $this->parseCallArgs($expr->args) . ')'; $callArgs = $this->parseCallArgs($expr->args);
return $call . '(' . $fn . ', ' . $callArgs . ')';
} catch (PlaceHolder) { } catch (PlaceHolder) {
return $this->genPlaceHolder($placeHolder); return $this->genPlaceHolder($placeHolder);
} }
@ -3561,12 +3591,23 @@ class CompilerBase extends \PhpAot\Core\Translator
{ {
$class = $this->parseIdentifier($expr->class); $class = $this->parseIdentifier($expr->class);
$self = false; $self = false;
if ($class === 'self' or $class === 'this_' or $class === 'static') { if ($class === 'self' or $class === 'this_') {
$self = true; $self = true;
$class = $this->class; $class = $this->class;
} }
$const = $this->escapeString($this->parseIdentifier($expr->name)); $const = $this->escapeString($this->parseIdentifier($expr->name));
if ($class === 'static') {
if (!$this->methodDef) {
$this->fatalError($expr, "The 'static' keyword can only be used as the class name in class methods");
}
if ($const === 'class') {
return 'php_get_called_class(this_)';
} else {
return 'php::constant(php_get_called_ce(), ' . $this->getLiteralString($const) . ')';
}
}
$class = $this->getNamespacedClassName($class); $class = $this->getNamespacedClassName($class);
if ($const === 'class') { if ($const === 'class') {
return '"' . $this->escapeString($class) . '"'; return '"' . $this->escapeString($class) . '"';
@ -3846,34 +3887,7 @@ class CompilerBase extends \PhpAot\Core\Translator
$classDef = $this->classDef; $classDef = $this->classDef;
} elseif (isset($this->objects[$object])) { } elseif (isset($this->objects[$object])) {
$class = $this->objects[$object]; $class = $this->objects[$object];
if (!$this->hasNativeClass($class)) { $nativeFunc = $this->getNativeMethod($expr, $class, $method);
return false;
}
$classDef = $this->getClassDef($class);
$methodDef = null;
// 递归查找,若子类中未定义方法,则尝试查找父类是否存在此方法
while ($classDef) {
if (!$classDef->hasMethod($method)) {
if (!$classDef->extends) {
return false;
}
$classDef = $this->getClassDef($classDef->extends);
} else {
$methodDef = $classDef->methods[$method];
break;
}
}
if (!$this->checkAccessible($classDef, $methodDef)) {
$this->fatalError($expr, 'Method `' . $classDef->getNamespacedName() . '::' . $method . '()` is not accessible');
}
if (count($expr->args) < $methodDef->functionDef->argCountRequired) {
$this->fatalError($expr, 'Method `' . $classDef->getNamespacedName() . '::' . $method . '()` requires ' . $methodDef->functionDef->argCountRequired . ' arguments, ' . count($expr->args) . ' given');
} elseif (count($expr->args) > count($methodDef->functionDef->argInfoList)) {
$this->fatalError($expr, 'Method `' . $classDef->getNamespacedName() . '::' . $method . '()` accepts ' . count($methodDef->functionDef->argInfoList) . ' arguments, ' . count($expr->args) . ' given');
}
$nativeFunc = $this->getNativeName($method, $classDef->namespace, $classDef->name);
} }
$fullMethodName = $classDef->getNamespacedName(false) . '::' . $method; $fullMethodName = $classDef->getNamespacedName(false) . '::' . $method;
// 存在子类同名方法,需要转为动态调用 // 存在子类同名方法,需要转为动态调用

@ -2,7 +2,8 @@
#if PPROF_ON #if PPROF_ON
#include <gperftools/profiler.h> #include <gperftools/profiler.h>
#endif #endif
#include <phpx.h>
#include <php_aot_helper.h>
extern zend_module_entry *php_embed_get_module(); extern zend_module_entry *php_embed_get_module();
@ -17,6 +18,23 @@ void module_init(zend_module_entry *module) {
} }
} }
const char *php_get_called_class(php::Object &this_) {
auto ce = php_get_called_ce(this_);
if (ce) {
return ce->name->val;
} else {
return "";
}
}
zend_class_entry *php_get_called_ce(php::Object &this_) {
if (this_.isObject()) {
return this_.ce();
} else {
return (zend_class_entry *) Z_PTR_P(this_.ptr());
}
}
void module_shutdown(zend_module_entry *module) { void module_shutdown(zend_module_entry *module) {
/** /**
* There is a bug in PHP's handling of internal strings. All interned strings are released in the request shutdown * There is a bug in PHP's handling of internal strings. All interned strings are released in the request shutdown

@ -1,4 +1,5 @@
#include <phpx.h> #include <phpx.h>
#include <phpx_helper.h>
#include <zend_attributes.h> #include <zend_attributes.h>
@ -6,6 +7,9 @@ extern zend_class_entry *php_get_class(int class_id, const php::Str &class_name)
extern zend_function *php_get_func(int func_id, const php::Str &func_name); extern zend_function *php_get_func(int func_id, const php::Str &func_name);
extern zend_function *php_get_method(int func_id, const php::Str &method_name, int class_id, const php::Str &class_name); extern zend_function *php_get_method(int func_id, const php::Str &method_name, int class_id, const php::Str &class_name);
extern const char *php_get_called_class(php::Object &this_);
extern zend_class_entry *php_get_called_ce(php::Object &this_);
static inline php::Variant CALL(int func_id, const php::Str &func_name) { static inline php::Variant CALL(int func_id, const php::Str &func_name) {
return php::call(php_get_func(func_id, func_name)); return php::call(php_get_func(func_id, func_name));
} }

@ -54,7 +54,7 @@ php::Str <?=Translator::LITERAL_STRINGS?>[] = {
<?php <?php
foreach ($this->literalStrings as $str => $index): foreach ($this->literalStrings as $str => $index):
?> ?>
php::String{ZEND_STRL("<?=$this->escapeString($str)?>"), true}, php::String{ZEND_STRL("<?=$this->escapeString($str)?>"), true}, // [<?=$index?>]
<?php endforeach; ?> <?php endforeach; ?>
}; };

@ -0,0 +1,40 @@
--TEST--
class static
--FILE--
<?php
namespace Test {
class Worker1
{
static function hello()
{
return "hello";
}
static function foo()
{
var_dump(self::hello());
var_dump(static::hello());
}
}
class Worker2 extends Worker1
{
static function hello()
{
return "world";
}
}
}
namespace {
use Test\Worker2;
function main()
{
Worker2::foo();
}
}
?>
--EXPECT--
string(5) "hello"
string(5) "world"
string(3) "foo"
Loading…
Cancel
Save