refactor(compiler): 优化方法调用和参数解析逻辑

- 移除编译器中无用的原生函数检查逻辑
- 更新反射参数检查以支持类方法参数引用
- 添加类型化对象的方法调用处理机制
- 实现匿名类实例继承相关功能
- 修复数组元素递增递减操作处理
- 优化ZipArchive等扩展函数的引用参数处理
- 添加私有属性名称解混淆测试用例
- 扩展反射类以支持类方法参数获取
pull/1/head
韩天峰 7 months ago
parent a1d74df985
commit 257616d571
  1. 5
      src/Php/AstNodeType.php
  2. 34
      src/Php/CompilerBase.php
  3. 48
      src/Php/Reflection.php
  4. 18
      tests/aot/array-item-dec-inc.phpt
  5. 42
      tests/aot/ref-call-arg.phpt
  6. 16
      tests/zend/anon/011.phpt
  7. 11
      tests/zend/anon/012.phpt

@ -49,6 +49,11 @@ trait AstNodeType
return $expr instanceof Node\Name;
}
protected function isNamedMethod(NodeAbstract $expr): bool
{
return $this->isIdExpr($expr);
}
protected function isScalarString(NodeAbstract $expr): bool
{
return $expr instanceof Node\Scalar\String_;

@ -1948,12 +1948,6 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseCallArgs(array $args, string $funcName = '', string $className = ''): string
{
if (!$className) {
if ($this->isNativeFunction($funcName)) {
return $this->parseNativeCallArgs($args, $funcName);
}
}
$list_args = [];
foreach ($args as $i => $arg) {
if ($arg->name !== null) {
@ -1961,7 +1955,7 @@ class CompilerBase extends \PhpAot\Core\Translator
}
if ($this->isVarExpr($arg->value)) {
$name = $this->parseIdentifier($arg->value);
if ($funcName and Reflection::isReferenceArg($funcName, $i)) {
if ($funcName and Reflection::isReferenceArg($funcName, $className, $i)) {
if (!$this->hasVar($name)) {
// 若参数是引用类型,可以传入未定义变量,将立即创建变量作为引用
$this->addLocalVar($name, self::TYPE_REF);
@ -1969,10 +1963,10 @@ class CompilerBase extends \PhpAot\Core\Translator
// 需要引用类型的参数,使用临时变量作为引用,并替换掉实际的参数
$tmpVar = $this->genTmpVarName();
$this->addLocalVar($tmpVar, self::TYPE_REF);
$this->beforeStmtLines[] = $tmpVar . ' = ' . $this->parseExpr($arg->value) . '.toReference();';
$name = $tmpVar;
}
$this->beforeStmtLines[] = $name . ' = ' . $this->parseExpr($arg->value) . '.toReference();';
$list_args[] = '&' . $name;
$list_args[] = '&' . $name;
continue;
}
if (!$this->hasVar($name)) {
@ -1983,7 +1977,7 @@ class CompilerBase extends \PhpAot\Core\Translator
if (!$this->hasVar($obj)) {
$this->fatalError($arg, 'Undefined variable `$' . $obj . '`');
}
if ($funcName and Reflection::isReferenceArg($funcName, $i)) {
if ($funcName and Reflection::isReferenceArg($funcName, $className, $i)) {
$list_args[] = $obj . '.attrRef(' . $this->identifierToStr($arg->value->name) . ')';
continue;
}
@ -1992,7 +1986,7 @@ class CompilerBase extends \PhpAot\Core\Translator
if ($this->isVarExpr($arg->value->var) and !$this->hasVar($array)) {
$this->fatalError($arg, 'Undefined variable `$' . $array . '`');
}
if ($funcName and Reflection::isReferenceArg($funcName, $i)) {
if ($funcName and Reflection::isReferenceArg($funcName, $className, $i)) {
if ($arg->value->dim === null) {
$this->fatalError($arg, 'Array dimension must be a constant expression');
}
@ -3122,16 +3116,26 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseMethodCall(Node\Expr\MethodCall $expr): string
{
$object = $this->convertToObject($expr->var);
$method = $this->parseIdentifier($expr->name);
$object = $this->convertToObject($expr->var);
if ($this->isTypedObject($object)) {
$class = $this->getObjectType($object);
} else {
$class = '';
}
$method = $this->identifierToStr($expr->name);
$nativeFunc = $this->findNativeMethod($expr, $object, $method);
if ($nativeFunc) {
return $this->parseNativeMethodCall($object, $nativeFunc, $expr->args);
}
if (empty($expr->args)) {
return $object . '.exec("' . $method . '")';
return $object . '.exec(' . $method . ')';
}
if ($this->isNamedMethod($expr->name)) {
$funcName = $this->parseIdentifier($expr->name);
} else {
$funcName = '';
}
return $object . '.exec("' . $method . '", ' . $this->parseCallArgs($expr->args) . ')';
return $object . '.exec(' . $method . ', ' . $this->parseCallArgs($expr->args, $funcName, $class) . ')';
}
protected function identifierToStr(NodeAbstract $node, bool $require = true): string

@ -8,15 +8,19 @@
namespace PhpAot\Php;
use ReflectionClass;
use ReflectionFunction;
class Reflection
{
private static array $functions = [];
private static array $classes = [];
public static function getFunction(string $fn)
public static function getFunction(string $fn): ?ReflectionFunction
{
if (!isset(self::$functions[$fn])) {
try {
$ref = new \ReflectionFunction($fn);
$ref = new ReflectionFunction($fn);
} catch (\ReflectionException $e) {
return null;
}
@ -26,6 +30,20 @@ class Reflection
return self::$functions[$fn];
}
public static function getClass(string $className): ?ReflectionClass
{
if (!isset(self::$classes[$className])) {
try {
$ref = new ReflectionClass($className);
} catch (\ReflectionException $e) {
return null;
}
self::$classes[$className] = $ref;
}
return self::$classes[$className];
}
public static function getFunctionReturnType(string $fn): ?string
{
$func = self::getFunction($fn);
@ -57,13 +75,33 @@ class Reflection
return $args[$index];
}
public static function isReferenceArg(string $fn, int $index): ?string
public static function getClassMethodParameter(string $className, string $fn, int $index): ?\ReflectionParameter
{
$param = self::getFunctionParameter($fn, $index);
if (!$param) {
$classRef = self::getClass($className);
if (!$classRef) {
return null;
}
$method = $classRef->getMethod($fn);
if (!$method) {
return null;
}
$args = $method->getParameters();
if ($index >= count($args)) {
return null;
}
return $args[$index];
}
public static function isReferenceArg(string $fnName, string $className, int $index): ?string
{
if ($className) {
$param = self::getClassMethodParameter($className, $fnName, $index);
} else {
$param = self::getFunctionParameter($fnName, $index);
}
if (!$param) {
return null;
}
return $param->isPassedByReference() ? $param->getName() : null;
}
}

@ -0,0 +1,18 @@
--TEST--
array item inc/dec
--FILE--
<?php
function main()
{
$array = array(1000);
var_dump($array[0]);
$array[0]--;
var_dump($array[0]);
$array[0]++;
var_dump($array[0]);
}
?>
--EXPECT--
int(1000)
int(999)
int(1000)

@ -0,0 +1,42 @@
--TEST--
ref call arg
--FILE--
<?php
function main()
{
$zip = new ZipArchive();
if ($zip->open(__DIR__ . '/../../examples/test.zip') === TRUE) {
for ($idx = 0; $s = $zip->statIndex($idx); $idx++) {
$rs = $zip->getExternalAttributesIndex($idx, $opsys, $attr);
var_dump($rs, $idx, $opsys, $attr);
}
$zip->close();
echo "OK\n";
}
$str = "first=value&arr[]=foo+bar&arr[]=baz";
parse_str($str, $output);
echo $output['first'], PHP_EOL; // value
echo $output['arr'][0], PHP_EOL; // foo bar
echo $output['arr'][1], PHP_EOL; // baz
echo "DONE\n";
}
?>
--EXPECT--
bool(true)
int(0)
int(3)
int(1107099648)
bool(true)
int(1)
int(3)
int(2176057344)
bool(true)
int(2)
int(3)
int(2176057344)
OK
value
foo bar
baz
DONE

@ -0,0 +1,16 @@
--TEST--
Ensure proper inheritance with get_class(anon class instance) used via class_alias (see also bug #70106)
--FILE--
<?php
function main() {
class_alias(get_class(new class { protected $foo = 1; }), "AnonBase");
var_dump((new class extends AnonBase {
function getFoo() {
return $this->foo;
}
})->getFoo());
}
?>
--EXPECT--
int(1)

@ -0,0 +1,11 @@
--TEST--
Ensure correct unmangling of private property names for anonymous class instances
--FILE--
<?php
var_dump(new class { private $foo; });
?>
--EXPECTF--
object(_anon_class_%s)#1 (1) {
["foo":"_anon_class_%s":private]=>
NULL
}
Loading…
Cancel
Save