From 952634c298a377c1f6b26ddd4ac5dbef9b878cf2 Mon Sep 17 00:00:00 2001 From: eric-therond Date: Wed, 10 Jan 2024 18:42:56 +0000 Subject: [PATCH] add support of php attributes --- .gitignore | 3 +- lib/PHPCfg/Op/Expr/Attribute.php | 34 ++++++ lib/PHPCfg/Op/Expr/AttributeGroup.php | 30 +++++ lib/PHPCfg/Op/Expr/Param.php | 6 +- lib/PHPCfg/Op/Stmt/ClassMethod.php | 4 +- lib/PHPCfg/Op/Stmt/Class_.php | 7 +- lib/PHPCfg/Op/Stmt/Function_.php | 10 +- lib/PHPCfg/Op/Stmt/Property.php | 7 +- lib/PHPCfg/Parser.php | 26 +++- lib/PHPCfg/Printer.php | 5 - test/PHPCfg/AttributesTest.php | 51 ++++++-- test/code/anonymous_class.test | 24 +++- test/code/class_attributes.test | 169 ++++++++++++++++++++++++++ test/code/function_attributes.test | 67 ++++++++++ test/code/property.test | 37 +++++- 15 files changed, 456 insertions(+), 24 deletions(-) create mode 100644 lib/PHPCfg/Op/Expr/Attribute.php create mode 100644 lib/PHPCfg/Op/Expr/AttributeGroup.php create mode 100644 test/code/class_attributes.test create mode 100644 test/code/function_attributes.test diff --git a/.gitignore b/.gitignore index 636cacc..8162295 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ composer.lock test.php vendor/ -.idea/ \ No newline at end of file +.idea/ +.phpunit.result.cache \ No newline at end of file diff --git a/lib/PHPCfg/Op/Expr/Attribute.php b/lib/PHPCfg/Op/Expr/Attribute.php new file mode 100644 index 0000000..6f58faa --- /dev/null +++ b/lib/PHPCfg/Op/Expr/Attribute.php @@ -0,0 +1,34 @@ +name = $this->addReadRef($name); + $this->args = $args; + } + + public function getVariableNames(): array + { + return ['name', 'args', 'result']; + } +} diff --git a/lib/PHPCfg/Op/Expr/AttributeGroup.php b/lib/PHPCfg/Op/Expr/AttributeGroup.php new file mode 100644 index 0000000..cf922c7 --- /dev/null +++ b/lib/PHPCfg/Op/Expr/AttributeGroup.php @@ -0,0 +1,30 @@ +attrs = $attrs; + } + + public function getVariableNames(): array + { + return ['attrs', 'result']; + } +} diff --git a/lib/PHPCfg/Op/Expr/Param.php b/lib/PHPCfg/Op/Expr/Param.php index 492dc11..bef385c 100755 --- a/lib/PHPCfg/Op/Expr/Param.php +++ b/lib/PHPCfg/Op/Expr/Param.php @@ -24,6 +24,8 @@ class Param extends Expr public bool $byRef; public bool $variadic; + + public array $attrGroups; public ?Operand $defaultVar = null; @@ -39,6 +41,7 @@ public function __construct( Op\Type $type, bool $byRef, bool $variadic, + array $attrGroups, ?Operand $defaultVar = null, ?Block $defaultBlock = null, array $attributes = [] @@ -49,6 +52,7 @@ public function __construct( $this->declaredType = $type; $this->byRef = $byRef; $this->variadic = $variadic; + $this->attrGroups = $attrGroups; if (!is_null($defaultVar)) { $this->defaultVar = $this->addReadRef($defaultVar); } @@ -57,7 +61,7 @@ public function __construct( public function getVariableNames(): array { - return ['name', 'defaultVar', 'result']; + return ['name', 'attrGroups', 'defaultVar', 'result']; } public function getSubBlocks(): array diff --git a/lib/PHPCfg/Op/Stmt/ClassMethod.php b/lib/PHPCfg/Op/Stmt/ClassMethod.php index e88253b..c448141 100644 --- a/lib/PHPCfg/Op/Stmt/ClassMethod.php +++ b/lib/PHPCfg/Op/Stmt/ClassMethod.php @@ -24,9 +24,9 @@ class ClassMethod extends Function_ public bool $abstract; - public function __construct(Func $func, int $visiblity, bool $static, bool $final, bool $abstract, array $attributes = []) + public function __construct(Func $func, int $visiblity, bool $static, bool $final, bool $abstract, array $attrGroups, array $attributes = []) { - parent::__construct($func, $attributes); + parent::__construct($func, $attrGroups, $attributes); $this->visibility = $visiblity; $this->static = $static; $this->final = $final; diff --git a/lib/PHPCfg/Op/Stmt/Class_.php b/lib/PHPCfg/Op/Stmt/Class_.php index cdaf5ce..5ad6ab1 100755 --- a/lib/PHPCfg/Op/Stmt/Class_.php +++ b/lib/PHPCfg/Op/Stmt/Class_.php @@ -22,16 +22,19 @@ class Class_ extends ClassLike public array $implements; - public function __construct(Operand $name, int $flags, ?Operand $extends, array $implements, Block $stmts, array $attributes = []) + public array $attrGroups; + + public function __construct(Operand $name, int $flags, ?Operand $extends, array $implements, Block $stmts, array $attrGroups, array $attributes = []) { parent::__construct($name, $stmts, $attributes); $this->flags = $flags; $this->extends = $extends; $this->implements = $implements; + $this->attrGroups = $attrGroups; } public function getVariableNames(): array { - return ['name', 'extends', 'implements']; + return ['name', 'attrGroups', 'extends', 'implements']; } } diff --git a/lib/PHPCfg/Op/Stmt/Function_.php b/lib/PHPCfg/Op/Stmt/Function_.php index 817e635..1d4d514 100755 --- a/lib/PHPCfg/Op/Stmt/Function_.php +++ b/lib/PHPCfg/Op/Stmt/Function_.php @@ -19,14 +19,22 @@ class Function_ extends Stmt implements CallableOp { public Func $func; - public function __construct(Func $func, array $attributes = []) + public array $attrGroups; + + public function __construct(Func $func, array $attrGroups, array $attributes = []) { parent::__construct($attributes); $this->func = $func; + $this->attrGroups = $attrGroups; } public function getFunc(): Func { return $this->func; } + + public function getVariableNames(): array + { + return ['attrGroups']; + } } diff --git a/lib/PHPCfg/Op/Stmt/Property.php b/lib/PHPCfg/Op/Stmt/Property.php index 794fab3..c037111 100755 --- a/lib/PHPCfg/Op/Stmt/Property.php +++ b/lib/PHPCfg/Op/Stmt/Property.php @@ -26,6 +26,8 @@ class Property extends Stmt public bool $static; public bool $readonly; + + public array $attrGroups; public ?Operand $defaultVar = null; @@ -33,13 +35,14 @@ class Property extends Stmt public Op\Type $declaredType ; - public function __construct(Operand $name, int $visiblity, bool $static, bool $readonly, Op\Type $declaredType = null, Operand $defaultVar = null, Block $defaultBlock = null, array $attributes = []) + public function __construct(Operand $name, int $visiblity, bool $static, bool $readonly, array $attrGroups, Op\Type $declaredType = null, Operand $defaultVar = null, Block $defaultBlock = null, array $attributes = []) { parent::__construct($attributes); $this->name = $this->addReadRef($name); $this->visibility = $visiblity; $this->static = $static; $this->readonly = $readonly; + $this->attrGroups = $attrGroups; $this->declaredType = $declaredType; if (!is_null($defaultVar)) { $this->defaultVar = $this->addReadRef($defaultVar); @@ -74,7 +77,7 @@ public function isReadonly() : bool public function getVariableNames(): array { - return ['name', 'defaultVar']; + return ['name', 'attrGroups', 'defaultVar']; } public function getSubBlocks(): array diff --git a/lib/PHPCfg/Parser.php b/lib/PHPCfg/Parser.php index 6a6762e..2aaa081 100755 --- a/lib/PHPCfg/Parser.php +++ b/lib/PHPCfg/Parser.php @@ -122,10 +122,16 @@ protected function parseFunc(Func $func, array $params, array $stmts) $start = $func->cfg; + $tmp = $this->block; + $this->block = $start; + $func->params = $this->parseParameterList($func, $params); foreach ($func->params as $param) { $this->writeVariableName($param->name->value, $param->result, $start); + $start->children[] = $param; } + + $this->block = $tmp; $end = $this->parseNodes($stmts, $start); @@ -226,6 +232,7 @@ protected function parseStmt_Class(Stmt\Class_ $node) $this->parseExprNode($node->extends), $this->parseExprList($node->implements), $this->parseNodes($node->stmts, new Block()), + $this->parseExprList($node->attrGroups), $this->mapAttributes($node) ); $this->currentClass = $old; @@ -282,6 +289,7 @@ protected function parseStmt_ClassMethod(Stmt\ClassMethod $node) (bool) $static, (bool) $final, (bool) $abstract, + $this->parseExprList($node->attrGroups), $this->mapAttributes($node) ); $func->callableOp = $class_method; @@ -416,7 +424,7 @@ protected function parseStmt_Function(Stmt\Function_ $node) null, ); $this->parseFunc($func, $node->params, $node->stmts, null); - $this->block->children[] = $function = new Op\Stmt\Function_($func, $this->mapAttributes($node)); + $this->block->children[] = $function = new Op\Stmt\Function_($func, $this->parseExprList($node->attrGroups), $this->mapAttributes($node)); $func->callableOp = $function; } @@ -571,6 +579,7 @@ protected function parseStmt_Property(Stmt\Property $node) $visibility, (bool) $static, (bool) $readonly, + $this->parseExprList($node->attrGroups), $this->parseTypeNode($node->type), $defaultVar, $defaultBlock, @@ -928,6 +937,20 @@ protected function parseArg(Node\Arg $expr) return $this->readVariable($this->parseExprNode($expr->value)); } + protected function parseAttribute(Node\Attribute $attr) + { + $args = array_map([$this, 'parseArg'], $attr->args); + + return new Op\Expr\Attribute($this->readVariable($this->parseExprNode($attr->name)), $args, $this->mapAttributes($attr)); + } + + protected function parseAttributeGroup(Node\AttributeGroup $attrGroup) + { + $attrs = $this->parseExprList($attrGroup->attrs); + + return new Op\Expr\AttributeGroup($attrs, $this->mapAttributes($attrGroup)); + } + protected function parseExpr_Array(Expr\Array_ $expr) { $keys = []; @@ -1547,6 +1570,7 @@ private function parseParameterList(Func $func, array $params) $this->parseTypeNode($param->type), $param->byRef, $param->variadic, + $this->parseExprList($param->attrGroups), $defaultVar, $defaultBlock, $this->mapAttributes($param) diff --git a/lib/PHPCfg/Printer.php b/lib/PHPCfg/Printer.php index 6960361..a5e2f44 100755 --- a/lib/PHPCfg/Printer.php +++ b/lib/PHPCfg/Printer.php @@ -266,11 +266,6 @@ protected function render(Func $func) while ($this->blockQueue->count() > 0) { $block = $this->blockQueue->dequeue(); $ops = []; - if ($block === $func->cfg) { - foreach ($func->params as $param) { - $renderedOps[$param] = $ops[] = $this->renderOp($param); - } - } foreach ($block->phi as $phi) { $result = $this->indent($this->renderOperand($phi->result).' = Phi('); $result .= implode(', ', array_map([$this, 'renderOperand'], $phi->vars)); diff --git a/test/PHPCfg/AttributesTest.php b/test/PHPCfg/AttributesTest.php index d3fc5b7..fd82068 100755 --- a/test/PHPCfg/AttributesTest.php +++ b/test/PHPCfg/AttributesTest.php @@ -64,6 +64,11 @@ public function testAttributes() function foo(\$a) { return \$a; } + +#[Attr] +function foowithattribute(\$a) { + return \$a; +} EOF; $expected = <<< EOF @@ -72,8 +77,25 @@ function foo(\$a) { attribute['filename']: foo.php attribute['startLine']: 2 attribute['endLine']: 4 + Expr_Attribute + attribute['filename']: foo.php + attribute['startLine']: 6 + attribute['endLine']: 6 + name: LITERAL('Attr') + result: Var#1 + Expr_AttributeGroup + attribute['filename']: foo.php + attribute['startLine']: 6 + attribute['endLine']: 6 + attrs[0]: Var#1 + result: Var#2 + Stmt_Function<'foowithattribute'> + attribute['filename']: foo.php + attribute['startLine']: 6 + attribute['endLine']: 9 + attrGroups[0]: Var#2 Terminal_Return - + Function 'foo': mixed Block#1 Expr_Param @@ -88,6 +110,21 @@ function foo(\$a) { attribute['startLine']: 3 attribute['endLine']: 3 expr: Var#1<\$a> + +Function 'foowithattribute': mixed +Block#1 + Expr_Param + attribute['filename']: foo.php + attribute['startLine']: 7 + attribute['endLine']: 7 + declaredType: mixed + name: LITERAL('a') + result: Var#1<\$a> + Terminal_Return + attribute['filename']: foo.php + attribute['startLine']: 8 + attribute['endLine']: 8 + expr: Var#1<\$a> EOF; $parser = new Parser((new ParserFactory())->create(ParserFactory::PREFER_PHP7), null); @@ -120,9 +157,9 @@ function foo(\$a) { Stmt_Function<'foo'> attribute['filename']: foo.php attribute['startLine']: 2 - attribute['startFilePos']: 6 + attribute['startFilePos']: 7 attribute['endLine']: 4 - attribute['endFilePos']: 40 + attribute['endFilePos']: 43 Terminal_Return Function 'foo': mixed @@ -130,18 +167,18 @@ function foo(\$a) { Expr_Param attribute['filename']: foo.php attribute['startLine']: 2 - attribute['startFilePos']: 19 + attribute['startFilePos']: 20 attribute['endLine']: 2 - attribute['endFilePos']: 20 + attribute['endFilePos']: 21 declaredType: mixed name: LITERAL('a') result: Var#1<\$a> Terminal_Return attribute['filename']: foo.php attribute['startLine']: 3 - attribute['startFilePos']: 29 + attribute['startFilePos']: 31 attribute['endLine']: 3 - attribute['endFilePos']: 38 + attribute['endFilePos']: 40 expr: Var#1<\$a> EOF; diff --git a/test/code/anonymous_class.test b/test/code/anonymous_class.test index f22cac6..a4c6b5f 100644 --- a/test/code/anonymous_class.test +++ b/test/code/anonymous_class.test @@ -4,6 +4,8 @@ $var = new class { echo "Hello World"; } }; + +$instance = new #[Attr('foo')] class {}; ----- Block#1 Stmt_Class @@ -16,14 +18,34 @@ Block#1 var: Var#2<$var> expr: Var#1 result: Var#3 + Expr_Attribute + name: LITERAL('Attr') + args[0]: LITERAL('foo') + result: Var#4 + Expr_AttributeGroup + attrs[0]: Var#4 + result: Var#5 + Stmt_Class + name: LITERAL('{anonymousClass}#2') + attrGroups[0]: Var#5 + stmts: Block#3 + Expr_New + class: LITERAL('{anonymousClass}#2') + result: Var#6 + Expr_Assign + var: Var#7<$instance> + expr: Var#6 + result: Var#8 Terminal_Return Block#2 Stmt_ClassMethod<'doSomething'> flags: public +Block#3 + Function '{anonymousClass}#1::doSomething': mixed Block#1 Terminal_Echo expr: LITERAL('Hello World') - Terminal_Return + Terminal_Return diff --git a/test/code/class_attributes.test b/test/code/class_attributes.test new file mode 100644 index 0000000..767db0d --- /dev/null +++ b/test/code/class_attributes.test @@ -0,0 +1,169 @@ + + flags: private + attrGroups[0]: Var#23 + +Block#3 + +Block#4 + +Block#5 + +Block#6 + +Block#7 + +Function 'NameOfClass1::method1': mixed +Block#1 + Expr_Attribute + name: LITERAL('FooParamAttrib') + args[0]: LITERAL('Foo1') + result: Var#1 + Expr_AttributeGroup + attrs[0]: Var#1 + result: Var#2 + Expr_Param + declaredType: mixed + name: LITERAL('foo') + attrGroups[0]: Var#2 + result: Var#3<$foo> + Terminal_Return diff --git a/test/code/function_attributes.test b/test/code/function_attributes.test new file mode 100644 index 0000000..c8fcbab --- /dev/null +++ b/test/code/function_attributes.test @@ -0,0 +1,67 @@ + + attrGroups[0]: Var#2 + Expr_Attribute + name: LITERAL('ConstAttr') + result: Var#3 + Expr_AttributeGroup + attrs[0]: Var#3 + result: Var#4 + Expr_ConstFetch + name: LITERAL('null') + result: Var#5 + Expr_Attribute + name: LITERAL('FooAttribute') + args[0]: Var#5 + result: Var#6 + Expr_AttributeGroup + attrs[0]: Var#6 + result: Var#7 + Stmt_Function<'foo5'> + attrGroups[0]: Var#4 + attrGroups[1]: Var#7 + Stmt_Function<'foo_func'> + Terminal_Return + +Function 'foo2': mixed +Block#1 + Terminal_Return + +Function 'foo5': mixed +Block#1 + Terminal_Return + +Function 'foo_func': mixed +Block#1 + Expr_Attribute + name: LITERAL('FooParamAttrib') + args[0]: LITERAL('Foo1') + result: Var#1 + Expr_AttributeGroup + attrs[0]: Var#1 + result: Var#2 + Expr_Param + declaredType: mixed + name: LITERAL('foo') + attrGroups[0]: Var#2 + result: Var#3<$foo> + Terminal_Return diff --git a/test/code/property.test b/test/code/property.test index 93d9585..ab7c207 100755 --- a/test/code/property.test +++ b/test/code/property.test @@ -7,6 +7,13 @@ class A { private readonly static $prop5; static $prop6; protected $prop7; + + #[ConstAttr] + #[FooAttribute(null)] + private string $foo5; + + #[NameOfAttribute] + private const FOO = 'foo'; } ----- Block#1 @@ -48,6 +55,32 @@ Block#2 flags: protected declaredType: mixed name: LITERAL('prop7') + Expr_Attribute + name: LITERAL('ConstAttr') + result: Var#2 + Expr_AttributeGroup + attrs[0]: Var#2 + result: Var#3 + Expr_ConstFetch + name: LITERAL('null') + result: Var#4 + Expr_Attribute + name: LITERAL('FooAttribute') + args[0]: Var#4 + result: Var#5 + Expr_AttributeGroup + attrs[0]: Var#5 + result: Var#6 + Stmt_Property + flags: private + declaredType: string + name: LITERAL('foo5') + attrGroups[0]: Var#3 + attrGroups[1]: Var#6 + Terminal_Const + name: LITERAL('FOO') + value: LITERAL('foo') + valueBlock: Block#5 Block#3 @@ -55,4 +88,6 @@ Block#4 Expr_BinaryOp_Plus left: LITERAL(1) right: LITERAL(1) - result: Var#1 \ No newline at end of file + result: Var#1 + +Block#5