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/Attributes/Attribute.php b/lib/PHPCfg/Op/Attributes/Attribute.php new file mode 100644 index 0000000..c3860b8 --- /dev/null +++ b/lib/PHPCfg/Op/Attributes/Attribute.php @@ -0,0 +1,29 @@ +name = $this->addReadRef($name); + $this->args = $args; + } +} diff --git a/lib/PHPCfg/Op/Attributes/AttributeGroup.php b/lib/PHPCfg/Op/Attributes/AttributeGroup.php new file mode 100644 index 0000000..94b8c33 --- /dev/null +++ b/lib/PHPCfg/Op/Attributes/AttributeGroup.php @@ -0,0 +1,25 @@ +attrs = $attrs; + } +} diff --git a/lib/PHPCfg/Op/Expr/Param.php b/lib/PHPCfg/Op/Expr/Param.php index 492dc11..ac2149c 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); } 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..4316a5d 100755 --- a/lib/PHPCfg/Op/Stmt/Class_.php +++ b/lib/PHPCfg/Op/Stmt/Class_.php @@ -22,12 +22,15 @@ 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 diff --git a/lib/PHPCfg/Op/Stmt/Function_.php b/lib/PHPCfg/Op/Stmt/Function_.php index 817e635..39eda17 100755 --- a/lib/PHPCfg/Op/Stmt/Function_.php +++ b/lib/PHPCfg/Op/Stmt/Function_.php @@ -19,10 +19,13 @@ 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 diff --git a/lib/PHPCfg/Op/Stmt/Property.php b/lib/PHPCfg/Op/Stmt/Property.php index 794fab3..4061578 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); diff --git a/lib/PHPCfg/Parser.php b/lib/PHPCfg/Parser.php index 6a6762e..75b154d 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); @@ -163,6 +169,7 @@ protected function parseNode(Node $node) return; } + $type = $node->getType(); if (method_exists($this, 'parse'.$type)) { $this->{'parse'.$type}($node); @@ -226,6 +233,7 @@ protected function parseStmt_Class(Stmt\Class_ $node) $this->parseExprNode($node->extends), $this->parseExprList($node->implements), $this->parseNodes($node->stmts, new Block()), + $this->parseAttributeGroups($node->attrGroups), $this->mapAttributes($node) ); $this->currentClass = $old; @@ -282,6 +290,7 @@ protected function parseStmt_ClassMethod(Stmt\ClassMethod $node) (bool) $static, (bool) $final, (bool) $abstract, + $this->parseAttributeGroups($node->attrGroups), $this->mapAttributes($node) ); $func->callableOp = $class_method; @@ -416,7 +425,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->parseAttributeGroups($node->attrGroups), $this->mapAttributes($node)); $func->callableOp = $function; } @@ -566,11 +575,13 @@ protected function parseStmt_Property(Stmt\Property $node) $defaultVar = null; $defaultBlock = null; } + $this->block->children[] = new Op\Stmt\Property( $this->parseExprNode($prop->name), $visibility, (bool) $static, (bool) $readonly, + $this->parseAttributeGroups($node->attrGroups), $this->parseTypeNode($node->type), $defaultVar, $defaultBlock, @@ -928,6 +939,25 @@ 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\Attributes\Attribute($this->readVariable($this->parseExprNode($attr->name)), $args, $this->mapAttributes($attr)); + } + + protected function parseAttributeGroup(Node\AttributeGroup $attrGroup) + { + $attrs = array_map([$this, 'parseAttribute'], $attrGroup->attrs); + + return new Op\Attributes\AttributeGroup($attrs, $this->mapAttributes($attrGroup)); + } + + protected function parseAttributeGroups(array $attrGroups) + { + return array_map([$this, 'parseAttributeGroup'], $attrGroups); + } + protected function parseExpr_Array(Expr\Array_ $expr) { $keys = []; @@ -1547,6 +1577,7 @@ private function parseParameterList(Func $func, array $params) $this->parseTypeNode($param->type), $param->byRef, $param->variadic, + $this->parseAttributeGroups($param->attrGroups), $defaultVar, $defaultBlock, $this->mapAttributes($param) diff --git a/lib/PHPCfg/Printer.php b/lib/PHPCfg/Printer.php index 6960361..82a5d7d 100755 --- a/lib/PHPCfg/Printer.php +++ b/lib/PHPCfg/Printer.php @@ -119,7 +119,12 @@ protected function renderOp(Op $op) $result .= $this->renderAttributes($op->getAttributes()); + if ($op instanceof Op\Stmt\Function_ || $op instanceof Op\Stmt\Class_) { + $result .= $this->renderAttrGroups($op->attrGroups); + } + if ($op instanceof Op\Stmt\Property) { + $result .= $this->renderAttrGroups($op->attrGroups); $result .= "\n flags: " . $this->indent($this->renderFlags($op)); $result .= "\n declaredType: " . $this->indent($this->renderType($op->declaredType)); } @@ -163,9 +168,11 @@ protected function renderOp(Op $op) } } if ($op instanceof Op\Stmt\ClassMethod) { + $result .= $this->renderAttrGroups($op->attrGroups); $result .= "\n flags: " . $this->indent($this->renderFlags($op)); } if ($op instanceof Op\Expr\Param) { + $result .= $this->renderAttrGroups($op->attrGroups); $result .= "\n declaredType: " . $this->indent($this->renderType($op->declaredType)); } if ($op instanceof Op\Expr\Include_) { @@ -266,11 +273,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)); @@ -394,4 +396,24 @@ public function renderAttributes(array $attributes): string return $result; } + + public function renderAttrGroups(array $attrGroups): string + { + $result = ''; + + foreach($attrGroups as $indexGroup => $attrGroup) { + $result .= "\n attrGroup[$indexGroup]: "; + $result .= $this->indent($this->renderAttributes($attrGroup->getAttributes())); + foreach($attrGroup->attrs as $indexAttr => $attr) { + $result .= "\n attr[$indexAttr]: "; + $result .= $this->indent($this->renderAttributes($attr->getAttributes()), 2); + $result .= "\n name: ".$this->renderOperand($attr->name); + foreach($attr->args as $indexArg => $arg) { + $result .= "\n args[$indexArg]: ".$this->renderOperand($arg); + } + } + } + + return $result; + } } diff --git a/test/PHPCfg/AttributesTest.php b/test/PHPCfg/AttributesTest.php index d3fc5b7..25b5d69 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,21 @@ function foo(\$a) { attribute['filename']: foo.php attribute['startLine']: 2 attribute['endLine']: 4 + Stmt_Function<'foowithattribute'> + attribute['filename']: foo.php + attribute['startLine']: 6 + attribute['endLine']: 9 + attrGroup[0]: + attribute['filename']: foo.php + attribute['startLine']: 6 + attribute['endLine']: 6 + attr[0]: + attribute['filename']: foo.php + attribute['startLine']: 6 + attribute['endLine']: 6 + name: LITERAL('Attr') Terminal_Return - + Function 'foo': mixed Block#1 Expr_Param @@ -88,6 +106,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); diff --git a/test/code/anonymous_class.test b/test/code/anonymous_class.test index f22cac6..730a2c3 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,30 @@ Block#1 var: Var#2<$var> expr: Var#1 result: Var#3 + Stmt_Class + attrGroup[0]: + attr[0]: + name: LITERAL('Attr') + args[0]: LITERAL('foo') + name: LITERAL('{anonymousClass}#2') + stmts: Block#3 + Expr_New + class: LITERAL('{anonymousClass}#2') + result: Var#4 + Expr_Assign + var: Var#5<$instance> + expr: Var#4 + result: Var#6 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..b296c30 --- /dev/null +++ b/test/code/class_attributes.test @@ -0,0 +1,132 @@ + + attrGroup[0]: + attr[0]: + name: LITERAL('ExampleAttributeMethod') + args[0]: LITERAL('foo') + args[1]: LITERAL('bar') + attrGroup[0]: + attr[0]: + name: LITERAL('ExampleAttributeMethod') + args[0]: LITERAL('foo') + args[1]: LITERAL('bar') + flags: private + +Block#3 + +Block#4 + +Block#5 + +Block#6 + +Block#7 + +Function 'NameOfClass1::method1': mixed +Block#1 + Expr_Param + attrGroup[0]: + attr[0]: + name: LITERAL('FooParamAttrib') + args[0]: LITERAL('Foo1') + declaredType: mixed + name: LITERAL('foo') + result: Var#1<$foo> + Terminal_Return \ No newline at end of file diff --git a/test/code/function_attributes.test b/test/code/function_attributes.test new file mode 100644 index 0000000..cbd3456 --- /dev/null +++ b/test/code/function_attributes.test @@ -0,0 +1,51 @@ + + attrGroup[0]: + attr[0]: + name: LITERAL('ExampleAttribute') + args[0]: LITERAL('foo') + args[1]: LITERAL('bar') + Expr_ConstFetch + name: LITERAL('null') + result: Var#1 + Stmt_Function<'foo5'> + attrGroup[0]: + attr[0]: + name: LITERAL('ConstAttr') + attrGroup[1]: + attr[0]: + name: LITERAL('FooAttribute') + args[0]: Var#1 + 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_Param + attrGroup[0]: + attr[0]: + name: LITERAL('FooParamAttrib') + args[0]: LITERAL('Foo1') + declaredType: mixed + name: LITERAL('foo') + result: Var#1<$foo> + Terminal_Return \ No newline at end of file diff --git a/test/code/property.test b/test/code/property.test index 93d9585..4f20229 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,24 @@ Block#2 flags: protected declaredType: mixed name: LITERAL('prop7') + Expr_ConstFetch + name: LITERAL('null') + result: Var#2 + Stmt_Property + attrGroup[0]: + attr[0]: + name: LITERAL('ConstAttr') + attrGroup[1]: + attr[0]: + name: LITERAL('FooAttribute') + args[0]: Var#2 + flags: private + declaredType: string + name: LITERAL('foo5') + Terminal_Const + name: LITERAL('FOO') + value: LITERAL('foo') + valueBlock: Block#5 Block#3 @@ -55,4 +80,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