diff --git a/examples/obj.php b/examples/obj.php new file mode 100644 index 00000000..8b6e787a --- /dev/null +++ b/examples/obj.php @@ -0,0 +1,27 @@ +a + $this->b; + } +} + +function main() +{ + $obj = new Test(); + $obj->a = 1; + $obj->b = 2; + + $arr = array(); + $arr['obj'] = $obj; + var_dump($obj->test()); + + $obj2 = objval($arr['obj'], 'Test'); + var_dump($obj2); + var_dump($obj2->test()); +} \ No newline at end of file diff --git a/src/Php/AstNodeType.php b/src/Php/AstNodeType.php index 4c7a8eea..a81acf47 100644 --- a/src/Php/AstNodeType.php +++ b/src/Php/AstNodeType.php @@ -27,4 +27,24 @@ trait AstNodeType { return $expr instanceof Expr\StaticPropertyFetch; } + + protected function isNewExpr(NodeAbstract $expr): bool + { + return $expr instanceof Expr\New_; + } + + protected function isNameExpr(NodeAbstract $expr): bool + { + return $expr instanceof Node\Name; + } + + protected function isScalarString(NodeAbstract $expr): bool + { + return $expr instanceof Node\Scalar\String_; + } + + protected function isFuncCallExpr(NodeAbstract $expr): bool + { + return $expr instanceof Expr\FuncCall; + } } \ No newline at end of file diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 61956e2d..fd382b53 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -39,7 +39,6 @@ class CompilerBase extends \PhpAot\Core\Translator public const string VALUE_NAN = 'std::numeric_limits::quiet_NaN()'; public const string VALUE_INF = 'std::numeric_limits::infinity()'; public const string LITERAL_STRINGS = '_literal_strings'; - public const string PROPERTY_OFFSETS = '_property_offsets'; public const string EXPR_VARIABLE = 'Expr_Variable'; public const string EXPR_NEW = 'Expr_New'; public const string EXPR_ARRAY_DIM_FETCH = 'Expr_ArrayDimFetch'; @@ -84,11 +83,19 @@ class CompilerBase extends \PhpAot\Core\Translator protected bool $useCppNamespace = false; protected string $file; protected string $dir; + /** + * 原始值,可能包含 `\\` 多层空间 + * @var string + */ protected string $namespace = ''; protected string $method = ''; protected string $function = ''; protected array $useNamespaces = []; protected array $useFunctions = []; + /** + * 原始类名,不包含命名空间 + * @var string + */ protected string $class = ''; protected string $interface = ''; /** @@ -124,6 +131,10 @@ class CompilerBase extends \PhpAot\Core\Translator 'argc' => self::TYPE_INT, 'argv' => self::TYPE_ARRAY, ]; + /** + * @var array + */ + protected array $objects = []; protected array $localVars = []; protected array $varTypes = []; protected array $objectWrappers = []; @@ -823,11 +834,28 @@ class CompilerBase extends \PhpAot\Core\Translator if ($var === 'this_') { $this->fatalError($left, 'Cannot re-assign $this'); } + $expr = $this->parseExpr($right); + $type = $this->detectExprType($right); + + if ($this->isVarExpr($left)) { + // 类型推断,获取对象的类名 + if ($this->isNewExpr($right) and $this->isNameExpr($right->class)) { + $class = $this->parseIdentifier($right->class); + $this->objects[$var] = $class; + $type = self::TYPE_OBJECT; + } elseif ($this->isFuncCallExpr($right) and $this->isNameExpr($right->name)) { + $fn = $this->parseIdentifier($right->name); + $arg2 = $right->args[1]->value; + if (count($right->args) === 2 and $fn === 'objval' and $this->isScalarString($arg2)) { + $this->objects[$var] = $arg2->value; + } + $type = self::TYPE_OBJECT; + } - if (!$this->hasVar($var)) { - $type = $this->detectExprType($right); - $this->addLocalVar($var, $type); + if (!$this->hasVar($var)) { + $this->addLocalVar($var, $type); + } } return $var . ' = ' . $this->convertExprType($expr, $this->detectExprType($left), $this->detectExprType($right)); } @@ -1730,10 +1758,14 @@ class CompilerBase extends \PhpAot\Core\Translator return $expr; } - protected function convertObjectExpr(string $expr): string + protected function convertObjectExpr(string $expr, string $class = ''): string { if (!$this->isClosedCall($expr, 'php::to_object')) { - return 'php::to_object(' . $this->trimBrackets($expr) . ')'; + if ($class === '') { + return 'php::to_object(' . $this->trimBrackets($expr) . ')'; + } else { + return 'php::to_object(' . $this->trimBrackets($expr) . ', ' . $class . ')'; + } } return $expr; } @@ -2385,8 +2417,8 @@ class CompilerBase extends \PhpAot\Core\Translator { $object = $this->convertToObject($expr->var); $method = $this->parseIdentifier($expr->name); - $nativeFunc = $this->getNativeName($method, $this->namespace, $this->class); - if ($this->isNativeMethod($object, $nativeFunc)) { + $nativeFunc = $this->findNativeMethod($expr, $object, $method); + if ($nativeFunc) { return $this->parseNativeMethodCall($object, $nativeFunc, $expr->args); } if (empty($expr->args)) { @@ -2715,17 +2747,45 @@ class CompilerBase extends \PhpAot\Core\Translator return 'continue;'; } - protected function isNativeMethod(string $object, string $nativeFunc): bool + protected function checkAccessible(ClassDef $classDef, MethodDef $methodDef): bool { + // 在当前类中,允许调用所有方法 + if ($classDef->namespace === $this->namespace and $classDef->name == $this->class) { + return true; + } + // 类外部调用,只允许调用 public 方法 + return $methodDef->flags & Modifiers::PUBLIC; + } + + protected function findNativeMethod(NodeAbstract $expr, string $object, string $method): string|false + { + $nativeFunc = ''; if ($object === 'this_') { - return $this->isNativeFunction($nativeFunc); + $nativeFunc = $this->getNativeName($method, $this->namespace, $this->class); + } elseif (isset($this->objects[$object])) { + $class = $this->objects[$object]; + if (!isset($this->classes[$class])) { + return false; + } + $classDef = $this->classes[$class]; + if (!isset($classDef->methods[$method])) { + return false; + } + $methodDef = $classDef->methods[$method]; + if (!$this->checkAccessible($classDef, $methodDef)) { + $this->fatalError($expr, 'Method `' . $classDef->getNamespacedName() . '::' . $method . '()` is not accessible'); + } + $nativeFunc = $this->getNativeName($method, $classDef->namespace, $classDef->name); + } + if ($nativeFunc and $this->isNativeFunction($nativeFunc)) { + return $nativeFunc; + } else { + return false; } - return false; } protected function parseNativeMethodCall(string $object, string $nativeFunc, array $args): string { - if (count($args) === 0) { return self::PREFIX . $nativeFunc . '(' . $object . ')'; } else { diff --git a/src/Php/FuncCallOptimizer.php b/src/Php/FuncCallOptimizer.php index b8e54925..5c7a37ca 100644 --- a/src/Php/FuncCallOptimizer.php +++ b/src/Php/FuncCallOptimizer.php @@ -19,6 +19,17 @@ trait FuncCallOptimizer return $this->convertFloatExpr($this->parseExpr($expr->args[0]->value)); case 'boolval': return $this->convertBoolExpr($this->parseExpr($expr->args[0]->value)); + case 'strval': + return $this->convertStringExpr($this->parseExpr($expr->args[0]->value)); + default: + break; + } + } elseif (count($expr->args) == 2) { + switch ($name) { + case 'objval': + $arg1 = $expr->args[0]->value; + $arg2 = $expr->args[1]->value; + return $this->convertObjectExpr($this->parseExpr($arg1), $this->parseExpr($arg2)); default: break; }