diff --git a/.gitignore b/.gitignore index 676b9a7..f2563ee 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ vendor/ .idea/ .phpunit.result.cache .php-cs-fixer.cache +coverage \ No newline at end of file diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index 25b5ade..eeb6fec 100755 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -11,6 +11,7 @@ ->name('.php_cs') ->exclude('vendor') ->exclude('.git') + ->exclude('coverage') ->in(__DIR__); return (new PhpCsFixer\Config()) @@ -23,5 +24,11 @@ 'comment_type' => 'PHPDoc', 'header' => $header, ], + 'fully_qualified_strict_types' => true, + 'global_namespace_import' => true, + 'no_unused_imports' => true, + 'ordered_imports' => true, + 'single_line_after_imports' => true, + 'single_import_per_statement' => true, ]) ->setFinder($finder); diff --git a/Makefile b/Makefile index a10bf0e..7c56b4e 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,8 @@ +SHELL := /bin/bash + +targets=$(shell for file in `find . -name '*Test.php' -type f -printf "%P\n"`; do echo "$$file "; done;) + + .PHONY: build build: cs-fix test @@ -7,6 +12,12 @@ cs-fix: .PHONY: test test: - XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-text + XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html ./coverage --display-deprecations + +.PHONY: t +t: +all: $(targets) +%Test.php: t + XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-text --display-deprecations $@ \ No newline at end of file diff --git a/README.md b/README.md index 5442d55..f3104e4 100644 --- a/README.md +++ b/README.md @@ -37,3 +37,11 @@ To dump the graph, simply use the built-in dumper: $dumper = new PHPCfg\Printer\Text(); echo $dumper->printScript($script); ``` + +## CLI + +You can leverage the CLI binary to generate debug traces of the CFG for any file, or for printing GraphViz visualizations. + +```shell +bin/php-cfg dot -o output.dot path/to/file.php +``` \ No newline at end of file diff --git a/bin/php-cfg b/bin/php-cfg new file mode 100755 index 0000000..08375e6 --- /dev/null +++ b/bin/php-cfg @@ -0,0 +1,13 @@ +#!/usr/bin/env php +add(new PHPCfg\Cli\PrintCommand, 'p'); +$app->add(new PHPCfg\Cli\DotCommand, 'd'); + +$app->add(new PHPCfg\Cli\RunTestCommand); + +$app->handle($_SERVER['argv']); // if argv[1] is `i` or `init` it executes InitCommand \ No newline at end of file diff --git a/composer.json b/composer.json index 0ede68e..d78f5ac 100755 --- a/composer.json +++ b/composer.json @@ -11,7 +11,8 @@ "require": { "php": ">=8.1", "nikic/php-parser": "^5.0", - "phpdocumentor/graphviz": "^1.0.4" + "phpdocumentor/graphviz": "^1.0.4", + "adhocore/cli": "^v1.0.0" }, "require-dev": { "phpunit/phpunit": ">=10.0", @@ -19,7 +20,11 @@ }, "autoload": { "psr-4": { - "PHPCfg\\": "lib/PHPCfg/" + "PHPCfg\\": "lib/PHPCfg/", + "PHPCfg\\Cli\\": "src/Cli" } - } + }, + "bin": [ + "bin/php-cfg" + ] } diff --git a/demo.php b/demo.php deleted file mode 100755 index c9fe009..0000000 --- a/demo.php +++ /dev/null @@ -1,60 +0,0 @@ -createForNewestSupportedVersion()); - -$declarations = new PHPCfg\Visitor\DeclarationFinder(); -$calls = new PHPCfg\Visitor\CallFinder(); -$variables = new PHPCfg\Visitor\VariableFinder(); - -$traverser = new PHPCfg\Traverser(); - -$traverser->addVisitor($declarations); -$traverser->addVisitor($calls); -$traverser->addVisitor(new PHPCfg\Visitor\Simplifier()); -$traverser->addVisitor($variables); - -$script = $parser->parse($code, __FILE__); -$traverser->traverse($script); - -if ($graphviz) { - $dumper = new PHPCfg\Printer\GraphViz(); - echo $dumper->printScript($script); -} else { - $dumper = new PHPCfg\Printer\Text(); - echo $dumper->printScript($script); -} - -function getCode($argc, $argv) -{ - if ($argc >= 2) { - if (strpos($argv[1], 'mode = $mode; - } elseif (! $value instanceof Operand) { - throw new \RuntimeException('Invalid value supplied for Assertion: '); + $this->setMode($mode); } else { - $this->mode = self::MODE_NONE; + $this->setMode(self::MODE_NONE); } $this->value = $value; } - public function getKind() + public function getKind(): string { return ''; } + + protected function setMode(int $mode): void + { + switch ($mode) { + case self::MODE_NONE: + case self::MODE_UNION: + case self::MODE_INTERSECTION: + break; + default: + throw new RuntimeException("Invalid mode supplied for Assertion"); + } + $this->mode = $mode; + } } diff --git a/lib/PHPCfg/Assertion/NegatedAssertion.php b/lib/PHPCfg/Assertion/NegatedAssertion.php index 3981081..cb2cf87 100644 --- a/lib/PHPCfg/Assertion/NegatedAssertion.php +++ b/lib/PHPCfg/Assertion/NegatedAssertion.php @@ -24,7 +24,7 @@ public function __construct($value) parent::__construct($value, self::MODE_INTERSECTION); } - public function getKind() + public function getKind(): string { return 'not'; } diff --git a/lib/PHPCfg/Assertion/TypeAssertion.php b/lib/PHPCfg/Assertion/TypeAssertion.php index d10dacd..8108ef6 100644 --- a/lib/PHPCfg/Assertion/TypeAssertion.php +++ b/lib/PHPCfg/Assertion/TypeAssertion.php @@ -15,7 +15,7 @@ class TypeAssertion extends Assertion { - public function getKind() + public function getKind(): string { return 'type'; } diff --git a/lib/PHPCfg/AstVisitor/LoopResolver.php b/lib/PHPCfg/AstVisitor/LoopResolver.php index c37b3e9..af7c5de 100755 --- a/lib/PHPCfg/AstVisitor/LoopResolver.php +++ b/lib/PHPCfg/AstVisitor/LoopResolver.php @@ -11,6 +11,7 @@ namespace PHPCfg\AstVisitor; +use LogicException; use PhpParser\Node; use PhpParser\Node\Scalar\LNumber; use PhpParser\Node\Stmt\Goto_; @@ -19,11 +20,11 @@ class LoopResolver extends NodeVisitorAbstract { - protected static $labelCounter = 0; + protected int $labelCounter = 0; - protected $continueStack = []; + protected array $continueStack = []; - protected $breakStack = []; + protected array $breakStack = []; public function enterNode(Node $node) { @@ -36,7 +37,6 @@ public function enterNode(Node $node) $lbl = $this->makeLabel(); $this->breakStack[] = $lbl; $this->continueStack[] = $lbl; - break; case 'Stmt_Do': case 'Stmt_While': @@ -57,11 +57,8 @@ public function leaveNode(Node $node) case 'Stmt_For': case 'Stmt_Foreach': $node->stmts[] = new Label(array_pop($this->continueStack)); - return [$node, new Label(array_pop($this->breakStack))]; case 'Stmt_Switch': - array_pop($this->continueStack); - return [$node, new Label(array_pop($this->breakStack))]; } } @@ -74,18 +71,17 @@ protected function resolveStack(Node $node, array $stack) if ($node->num instanceof LNumber) { $num = $node->num->value - 1; if ($num >= count($stack)) { - throw new \LogicException('Too high of a count for ' . $node->getType()); + throw new LogicException('Too high of a count for ' . $node->getType()); } - $loc = array_slice($stack, -1 * $num, 1); - + $loc = array_slice($stack, -1 * $num - 1, 1); return new Goto_($loc[0], $node->getAttributes()); } - throw new \LogicException('Unimplemented Node Value Type'); + throw new LogicException('Unimplemented Node Value Type'); } protected function makeLabel() { - return 'compiled_label_' . mt_rand(0, mt_getrandmax()) . '_' . self::$labelCounter++; + return 'compiled_label_' . mt_rand(0, mt_getrandmax()) . '_' . $this->labelCounter++; } } diff --git a/lib/PHPCfg/AstVisitor/MagicStringResolver.php b/lib/PHPCfg/AstVisitor/MagicStringResolver.php index 87da11b..789916f 100644 --- a/lib/PHPCfg/AstVisitor/MagicStringResolver.php +++ b/lib/PHPCfg/AstVisitor/MagicStringResolver.php @@ -17,13 +17,15 @@ class MagicStringResolver extends NodeVisitorAbstract { - protected $classStack = []; + protected array $namespaceStack = []; - protected $parentStack = []; + protected array $classStack = []; - protected $functionStack = []; + protected array $parentStack = []; - protected $methodStack = []; + protected array $functionStack = []; + + protected array $methodStack = []; public function enterNode(Node $node) { @@ -36,9 +38,9 @@ public function enterNode(Node $node) } else { $this->parentStack[] = ''; } - } - $this->repairComments($node); - if ($node instanceof Node\Stmt\Function_) { + } elseif ($node instanceof Node\Stmt\Namespace_) { + $this->namespaceStack[] = $node->name->toString(); + } elseif ($node instanceof Node\Stmt\Function_) { $this->functionStack[] = $node->namespacedName->toString(); } elseif ($node instanceof Node\Stmt\ClassMethod) { $this->methodStack[] = end($this->classStack) . '::' . $node->name; @@ -65,9 +67,7 @@ public function enterNode(Node $node) return new Node\Scalar\String_(end($this->classStack), $node->getAttributes()); } } elseif ($node instanceof Node\Scalar\MagicConst\Namespace_) { - if (! empty($this->classStack)) { - return new Node\Scalar\String_($this->stripClass(end($this->classStack)), $node->getAttributes()); - } + return new Node\Scalar\String_(end($this->namespaceStack), $node->getAttributes()); } elseif ($node instanceof Node\Scalar\MagicConst\Function_) { if (! empty($this->functionStack)) { return new Node\Scalar\String_(end($this->functionStack), $node->getAttributes()); @@ -93,17 +93,12 @@ public function leaveNode(Node $node) } elseif ($node instanceof Node\Stmt\ClassMethod) { assert(end($this->methodStack) === end($this->classStack) . '::' . $node->name); array_pop($this->methodStack); + } elseif ($node instanceof Node\Stmt\Namespace_) { + assert(end($this->namespaceStack) === $node->name->toString()); + array_pop($this->namespaceStack); } } - private function stripClass($class) - { - $parts = explode('\\', $class); - array_pop($parts); - - return implode('\\', $parts); - } - private function repairComments(Node $node) { $comment = $node->getDocComment(); diff --git a/lib/PHPCfg/AstVisitor/NameResolver.php b/lib/PHPCfg/AstVisitor/NameResolver.php index 6c15f59..cb75198 100644 --- a/lib/PHPCfg/AstVisitor/NameResolver.php +++ b/lib/PHPCfg/AstVisitor/NameResolver.php @@ -18,7 +18,7 @@ class NameResolver extends NameResolverParent { - protected static $builtInTypes = [ + private const BUILTIN_TYPES = [ 'self', 'parent', 'static', @@ -43,7 +43,7 @@ class NameResolver extends NameResolverParent 'callable', ]; - protected $anonymousClasses = 0; + protected int $anonymousClasses = 0; public function enterNode(Node $node) { @@ -51,8 +51,8 @@ public function enterNode(Node $node) if ($node instanceof Node\Stmt\Class_ && is_null($node->name)) { $anonymousName = "{anonymousClass}#" . ++$this->anonymousClasses; - $node->name = new \PhpParser\Node\Identifier($anonymousName); - $node->namespacedName = new \PhpParser\Node\Name($anonymousName); + $node->name = new Node\Identifier($anonymousName); + $node->namespacedName = new Name($anonymousName); } $comment = $node->getDocComment(); @@ -64,7 +64,6 @@ public function enterNode(Node $node) $regex, function ($match) { $type = $this->parseTypeDecl($match[2]); - return "@{$match[1]} {$type}"; }, $comment->getText(), @@ -103,7 +102,7 @@ protected function parseTypeDecl($type) if (! preg_match($regex, $type)) { return $type; // malformed Type, return original string } - if (in_array(strtolower($type), self::$builtInTypes, true)) { + if (in_array(strtolower($type), self::BUILTIN_TYPES, true)) { return $type; } // Now, we need to resolve the type diff --git a/lib/PHPCfg/Block.php b/lib/PHPCfg/Block.php index 7d7c8cf..5b69d2b 100755 --- a/lib/PHPCfg/Block.php +++ b/lib/PHPCfg/Block.php @@ -14,17 +14,17 @@ class Block { /** @var Op[] */ - public $children = []; + public array $children = []; /** @var Block[] */ - public $parents = []; + public array $parents = []; public ?CatchTarget $catchTarget; /** @var Op\Phi[] */ - public $phi = []; + public array $phi = []; - public $dead = false; + public bool $dead = false; public function __construct(?self $parent = null, ?CatchTarget $catchTarget = null) { @@ -39,13 +39,13 @@ public function __construct(?self $parent = null, ?CatchTarget $catchTarget = nu $this->setCatchTargetParents(); } - public function setCatchTarget(?CatchTarget $catchTarget) + public function setCatchTarget(?CatchTarget $catchTarget): void { $this->catchTarget = $catchTarget; $this->setCatchTargetParents(); } - public function setCatchTargetParents() + public function setCatchTargetParents(): void { if ($this->catchTarget) { $this->catchTarget->finally->addParent($this); @@ -55,7 +55,7 @@ public function setCatchTargetParents() } } - public function addParent(self $parent) + public function addParent(self $parent): void { if (! in_array($parent, $this->parents, true)) { $this->parents[] = $parent; diff --git a/lib/PHPCfg/CatchTarget.php b/lib/PHPCfg/CatchTarget.php index 51ee76c..8af8419 100755 --- a/lib/PHPCfg/CatchTarget.php +++ b/lib/PHPCfg/CatchTarget.php @@ -21,7 +21,7 @@ public function __construct(Block $finally) $this->finally = $finally; } - public function addCatch(Op $type, Operand $var, Block $block) + public function addCatch(Op $type, Operand $var, Block $block): void { $this->catches[] = [ "type" => $type, diff --git a/lib/PHPCfg/Func.php b/lib/PHPCfg/Func.php index a7490f9..35845e4 100755 --- a/lib/PHPCfg/Func.php +++ b/lib/PHPCfg/Func.php @@ -33,26 +33,20 @@ class Func extends Op public const FLAG_CLOSURE = 0x80; - /** @var string */ - public $name; + public string $name; - /** @var int */ - public $flags; + public int $flags; - /** @var */ public Op\Type $returnType; - /** @var Op\Type\Literal */ - public $class; + public ?Op\Type\Literal $class; /** @var Op\Expr\Param[] */ - public $params; + public array $params; - /** @var Block|null */ - public $cfg; + public ?Block $cfg; - /** @var CallableOp|null */ - public $callableOp; + public ?CallableOp $callableOp; public function __construct(string $name, int $flags, Op\Type $returnType, ?Op\Type\Literal $class, array $attributes = []) { @@ -65,7 +59,7 @@ public function __construct(string $name, int $flags, Op\Type $returnType, ?Op\T $this->cfg = new Block(); } - public function getScopedName() + public function getScopedName(): string { if (null !== $this->class) { return $this->class->name . '::' . $this->name; diff --git a/lib/PHPCfg/FuncContext.php b/lib/PHPCfg/FuncContext.php index 3d36389..48ba358 100644 --- a/lib/PHPCfg/FuncContext.php +++ b/lib/PHPCfg/FuncContext.php @@ -11,33 +11,31 @@ namespace PHPCfg; +use SplObjectStorage; + /** * Stores per-function compiler state. */ class FuncContext { /** @var Block[] */ - public $labels = []; + public array $labels = []; - /** @var \SplObjectStorage */ - public $scope; + public SplObjectStorage $scope; - /** @var \SplObjectStorage */ - public $incompletePhis; + public SplObjectStorage $incompletePhis; - /** @var bool */ - public $complete = false; + public bool $complete = false; - /** @var array[] */ - public $unresolvedGotos = []; + public array $unresolvedGotos = []; public function __construct() { - $this->scope = new \SplObjectStorage(); - $this->incompletePhis = new \SplObjectStorage(); + $this->scope = new SplObjectStorage(); + $this->incompletePhis = new SplObjectStorage(); } - public function setValueInScope(Block $block, $name, Operand $value) + public function setValueInScope(Block $block, $name, Operand $value): void { if (! isset($this->scope[$block])) { $this->scope[$block] = []; @@ -48,7 +46,7 @@ public function setValueInScope(Block $block, $name, Operand $value) $this->scope[$block] = $vars; } - public function isLocalVariable(Block $block, $name) + public function isLocalVariable(Block $block, string $name): bool { if (! isset($this->scope[$block])) { return false; @@ -58,7 +56,7 @@ public function isLocalVariable(Block $block, $name) return isset($vars[$name]); } - public function addToIncompletePhis(Block $block, $name, Op\Phi $phi) + public function addToIncompletePhis(Block $block, string $name, Op\Phi $phi): void { if (! isset($this->incompletePhis[$block])) { $this->incompletePhis[$block] = []; diff --git a/lib/PHPCfg/Op.php b/lib/PHPCfg/Op.php index f907bce..35e2e83 100755 --- a/lib/PHPCfg/Op.php +++ b/lib/PHPCfg/Op.php @@ -81,35 +81,23 @@ public function isWriteVariable(string $name): bool return in_array($name, $this->writeVariables, true); } - protected function addReadRefs(Operand ...$op): array + protected function addReadRefs(Operand ...$operand): array { $result = []; - foreach ($op as $key => $o) { + foreach ($operand as $key => $o) { $result[] = $this->addReadRef($o); } return $result; } - protected function addReadRef(Operand $op): Operand + protected function addReadRef(Operand $operand): Operand { - return $op->addUsage($this); + return $operand->addUsage($this); } - protected function addWriteRef(Operand $op): Operand + protected function addWriteRef(Operand $operand): Operand { - if (is_array($op)) { - $new = []; - foreach ($op as $key => $o) { - $new[$key] = $this->addWriteRef($o); - } - - return $new; - } - if (! $op instanceof Operand) { - return $op; - } - - return $op->addWriteOp($this); + return $operand->addWriteOp($this); } } diff --git a/lib/PHPCfg/Op/Expr/BinaryOp/Pipe.php b/lib/PHPCfg/Op/Expr/BinaryOp/Pipe.php new file mode 100644 index 0000000..ca1e3e9 --- /dev/null +++ b/lib/PHPCfg/Op/Expr/BinaryOp/Pipe.php @@ -0,0 +1,16 @@ +expr = $expr; + } + + public function getVariableNames(): array + { + return ['expr' => $this->expr, 'result' => $this->result]; + } +} diff --git a/lib/PHPCfg/Op/Stmt/ClassLike.php b/lib/PHPCfg/Op/Stmt/ClassLike.php index b615066..44ca8df 100755 --- a/lib/PHPCfg/Op/Stmt/ClassLike.php +++ b/lib/PHPCfg/Op/Stmt/ClassLike.php @@ -12,10 +12,10 @@ namespace PHPCfg\Op\Stmt; use PhpCfg\Block; -use PHPCfg\Op\Stmt; +use PHPCfg\Op; use PHPCfg\Op\AttributableOp; use PHPCfg\Op\Attributes\Attributable; -use PHPCfg\Op; +use PHPCfg\Op\Stmt; abstract class ClassLike extends Stmt implements AttributableOp { diff --git a/lib/PHPCfg/Op/Stmt/Function_.php b/lib/PHPCfg/Op/Stmt/Function_.php index a7f44a6..1b59b4f 100755 --- a/lib/PHPCfg/Op/Stmt/Function_.php +++ b/lib/PHPCfg/Op/Stmt/Function_.php @@ -12,9 +12,9 @@ namespace PHPCfg\Op\Stmt; use PHPCfg\Func; -use PHPCfg\Op\CallableOp; use PHPCfg\Op\AttributableOp; use PHPCfg\Op\Attributes\Attributable; +use PHPCfg\Op\CallableOp; use PHPCfg\Op\Stmt; class Function_ extends Stmt implements CallableOp, AttributableOp diff --git a/lib/PHPCfg/Op/Stmt/MatchTable.php b/lib/PHPCfg/Op/Stmt/MatchTable.php new file mode 100644 index 0000000..79ebbe0 --- /dev/null +++ b/lib/PHPCfg/Op/Stmt/MatchTable.php @@ -0,0 +1,46 @@ +cond = $this->addReadRef($cond); + } + + public function getVariableNames(): array + { + return ['cond' => $this->cond, 'armConditions' => $this->armConditions]; + } + + public function addArm(Operand $val, Block $block): void + { + $this->armConditions[] = $val; + $this->armBlocks[] = $block; + } + + public function getSubBlocks(): array + { + return ['armBlocks' => $this->armBlocks]; + } +} diff --git a/lib/PHPCfg/Op/Stmt/Property.php b/lib/PHPCfg/Op/Stmt/Property.php index e594714..be5c1af 100755 --- a/lib/PHPCfg/Op/Stmt/Property.php +++ b/lib/PHPCfg/Op/Stmt/Property.php @@ -12,10 +12,10 @@ namespace PHPCfg\Op\Stmt; use PHPCfg\Block; -use PHPCfg\Op\Stmt; use PHPCfg\Op; -use PHPCfg\Op\Attributes\Attributable; use PHPCfg\Op\AttributableOp; +use PHPCfg\Op\Attributes\Attributable; +use PHPCfg\Op\Stmt; use PHPCfg\Operand; use PhpParser\Modifiers; diff --git a/lib/PHPCfg/Op/Stmt/Try_.php b/lib/PHPCfg/Op/Stmt/Try_.php index fee245e..e9fcb13 100644 --- a/lib/PHPCfg/Op/Stmt/Try_.php +++ b/lib/PHPCfg/Op/Stmt/Try_.php @@ -11,8 +11,8 @@ namespace PHPCfg\Op\Stmt; -use PHPCfg\Op\Stmt; use PHPCfg\Block; +use PHPCfg\Op\Stmt; class Try_ extends Stmt { diff --git a/lib/PHPCfg/Op/Terminal/MatchError.php b/lib/PHPCfg/Op/Terminal/MatchError.php new file mode 100644 index 0000000..39d84a0 --- /dev/null +++ b/lib/PHPCfg/Op/Terminal/MatchError.php @@ -0,0 +1,31 @@ +cond = $this->addReadRef($cond); + } + + public function getVariableNames(): array + { + return ['cond' => $this->cond]; + } +} diff --git a/lib/PHPCfg/Op/TraitUseAdaptation/Alias.php b/lib/PHPCfg/Op/TraitUseAdaptation/Alias.php index 8078ba1..5188cf8 100644 --- a/lib/PHPCfg/Op/TraitUseAdaptation/Alias.php +++ b/lib/PHPCfg/Op/TraitUseAdaptation/Alias.php @@ -11,9 +11,9 @@ namespace PHPCfg\Op\TraitUseAdaptation; -use PhpParser\Modifiers; -use PHPCfg\Operand; use PHPCfg\Op\TraitUseAdaptation; +use PHPCfg\Operand; +use PhpParser\Modifiers; class Alias extends TraitUseAdaptation { diff --git a/lib/PHPCfg/Op/TraitUseAdaptation/Precedence.php b/lib/PHPCfg/Op/TraitUseAdaptation/Precedence.php index d0733c6..44e554f 100644 --- a/lib/PHPCfg/Op/TraitUseAdaptation/Precedence.php +++ b/lib/PHPCfg/Op/TraitUseAdaptation/Precedence.php @@ -11,8 +11,8 @@ namespace PHPCfg\Op\TraitUseAdaptation; -use PHPCfg\Operand; use PHPCfg\Op\TraitUseAdaptation; +use PHPCfg\Operand; class Precedence extends TraitUseAdaptation { diff --git a/lib/PHPCfg/Op/Type.php b/lib/PHPCfg/Op/Type.php index 6063644..73c114d 100755 --- a/lib/PHPCfg/Op/Type.php +++ b/lib/PHPCfg/Op/Type.php @@ -12,6 +12,5 @@ namespace PHPCfg\Op; use PHPCfg\Op; -use PHPCfg\Operand; abstract class Type extends Op {} diff --git a/lib/PHPCfg/Op/Type/Intersection.php b/lib/PHPCfg/Op/Type/Intersection.php new file mode 100644 index 0000000..b154692 --- /dev/null +++ b/lib/PHPCfg/Op/Type/Intersection.php @@ -0,0 +1,29 @@ +subtypes = $subtypes; + } + + public function getTypeNames(): array + { + return ['subtypes' => $this->subtypes]; + } +} diff --git a/lib/PHPCfg/Operand/BoundVariable.php b/lib/PHPCfg/Operand/BoundVariable.php index 44e74d1..b01c710 100644 --- a/lib/PHPCfg/Operand/BoundVariable.php +++ b/lib/PHPCfg/Operand/BoundVariable.php @@ -23,9 +23,9 @@ class BoundVariable extends Variable public const SCOPE_FUNCTION = 4; - public $byRef; + public bool $byRef; - public $scope; + public int $scope; public ?Op\Type\Literal $extra; diff --git a/lib/PHPCfg/Operand/Temporary.php b/lib/PHPCfg/Operand/Temporary.php index 1534f42..c90ac59 100644 --- a/lib/PHPCfg/Operand/Temporary.php +++ b/lib/PHPCfg/Operand/Temporary.php @@ -15,7 +15,7 @@ class Temporary extends Operand { - public $original; + public ?Operand $original; /** * Constructs a temporary variable diff --git a/lib/PHPCfg/Parser.php b/lib/PHPCfg/Parser.php index 392c2f9..bcf7dac 100755 --- a/lib/PHPCfg/Parser.php +++ b/lib/PHPCfg/Parser.php @@ -11,23 +11,18 @@ namespace PHPCfg; -use PHPCfg\Op\Stmt\Jump; -use PHPCfg\Op\Stmt\JumpIf; -use PHPCfg\Op\Stmt\TraitUse; -use PHPCfg\Op\Stmt\Try_; +use LogicException; use PHPCfg\Op\Terminal\Return_; -use PHPCfg\Op\TraitUseAdaptation\Alias; -use PHPCfg\Op\TraitUseAdaptation\Precedence; use PHPCfg\Operand\Literal; use PHPCfg\Operand\Temporary; use PHPCfg\Operand\Variable; -use PhpParser\Modifiers; use PhpParser\Node; use PhpParser\Node\Expr; -use PhpParser\Node\Expr\BinaryOp as AstBinaryOp; -use PhpParser\Node\Stmt; use PhpParser\NodeTraverser as AstTraverser; use PhpParser\Parser as AstParser; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use RuntimeException; class Parser { @@ -38,25 +33,27 @@ class Parser public const MODE_WRITE = 2; /** @var Block */ - protected $block; + public ?Block $block = null; protected $astParser; protected $astTraverser; - protected $fileName; + public string $fileName; - /** @var FuncContext */ - protected $ctx; + public ?FuncContext $ctx = null; - protected ?Op\Type\Literal $currentClass = null; + public ?Op\Type\Literal $currentClass = null; - protected ?Node\Name $currentNamespace = null; + public ?Node\Name $currentNamespace = null; - /** @var Script */ - protected $script; + public ?Script $script; - protected $anonId = 0; + public $anonId = 0; + + protected array $stmtHandlers = []; + protected array $exprHandlers = []; + protected array $opHandlers = []; public function __construct(AstParser $astParser, ?AstTraverser $astTraverser = null) { @@ -68,23 +65,60 @@ public function __construct(AstParser $astParser, ?AstTraverser $astTraverser = $this->astTraverser->addVisitor(new AstVisitor\NameResolver()); $this->astTraverser->addVisitor(new AstVisitor\LoopResolver()); $this->astTraverser->addVisitor(new AstVisitor\MagicStringResolver()); + $this->loadHandlers(); } - /** - * @param string $code - * @param string $fileName - * @returns Script - */ - public function parse($code, $fileName) + public function addHandler(string $name, ParserHandler $handler): void + { + if ($handler instanceof ParserHandler\Batch) { + foreach ($handler->getExprSupport() as $name) { + $this->exprHandlers[$name] = $handler; + } + foreach ($handler->getStmtSupport() as $name) { + $this->stmtHandlers[$name] = $handler; + } + return; + } + if ($handler instanceof ParserHandler\Expr) { + $this->exprHandlers[$name] = $handler; + } + if ($handler instanceof ParserHandler\Stmt) { + $this->stmtHandlers[$name] = $handler; + } + } + + protected function loadHandlers(): void + { + $it = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( + __DIR__ . '/ParserHandler/', + RecursiveIteratorIterator::LEAVES_ONLY + ) + ); + $handlers = []; + foreach ($it as $file) { + if (!$file->isFile() || $file->getExtension() !== 'php') { + continue; + } + $class = str_replace(__DIR__, '', $file->getPathname()); + $class = __NAMESPACE__ . str_replace("/", "\\", $class); + $class = substr($class, 0, -4); + + if (!class_exists($class)) { + continue; + } + + $obj = new $class($this); + $this->addHandler($obj->getName(), $obj); + } + } + + public function parse(string $code, string $fileName): Script { return $this->parseAst($this->astParser->parse($code), $fileName); } - /** - * @param array $ast PHP-Parser AST - * @param string $fileName - */ - public function parseAst($ast, $fileName): Script + protected function parseAst(array $ast, string $fileName): Script { $this->fileName = $fileName; $ast = $this->astTraverser->traverse($ast); @@ -102,7 +136,7 @@ public function parseAst($ast, $fileName): Script return $script; } - public function parseNodes(array $nodes, Block $block) + public function parseNodes(array $nodes, Block $block): Block { $tmp = $this->block; $this->block = $block; @@ -115,7 +149,7 @@ public function parseNodes(array $nodes, Block $block) return $end; } - protected function parseFunc(Func $func, array $params, array $stmts) + public function parseFunc(Func $func, array $params, array $stmts): void { // Switch to new function context $prevCtx = $this->ctx; @@ -141,7 +175,9 @@ protected function parseFunc(Func $func, array $params, array $stmts) } if ($this->ctx->unresolvedGotos) { - $this->throwUndefinedLabelError(); + foreach ($this->ctx->unresolvedGotos as $name => $_) { + throw new RuntimeException("'goto' to undefined label '{$name}'"); + } } $this->ctx->complete = true; @@ -163,663 +199,63 @@ protected function parseFunc(Func $func, array $params, array $stmts) $this->ctx = $prevCtx; } - protected function parseNode(Node $node) + public function parseNode(Node $node): void { - if ($node instanceof Node\Expr) { + if ($node instanceof Expr) { $this->parseExprNode($node); return; } $type = $node->getType(); - if (method_exists($this, 'parse' . $type)) { - $this->{'parse' . $type}($node); - + if (isset($this->stmtHandlers[$type])) { + $this->stmtHandlers[$type]->handleStmt($node); return; } - throw new \RuntimeException('Unknown Node Encountered : ' . $type); + throw new RuntimeException('Unknown Node Encountered : ' . $type); } - protected function parseTypeList(array $types): array + public function parseTypeList(?Node ...$types): array { - $parsedTypes = []; - foreach ($types as $type) { - $parsedTypes[] = $this->parseTypeNode($type); - } - - return $parsedTypes; + return array_map([$this, 'parseTypeNode'], $types); } - protected function parseTypeNode(?Node $node): Op\Type + public function parseTypeNode(?Node $node): Op\Type { if (is_null($node)) { return new Op\Type\Mixed_(); } - if ($node instanceof Node\Name) { - return new Op\Type\Literal( - $node->name, - $this->mapAttributes($node), - ); - } - if ($node instanceof Node\NullableType) { - return new Op\Type\Nullable( - $this->parseTypeNode($node->type), - $this->mapAttributes($node), - ); - } - if ($node instanceof Node\UnionType) { - $parsedTypes = []; - foreach ($node->types as $type) { - $parsedTypes[] = $this->parseTypeNode($type); - } - - return new Op\Type\Union( - $parsedTypes, - $this->mapAttributes($node), - ); - } - if ($node instanceof Node\Identifier) { - return new Op\Type\Literal( - $node->name, - $this->mapAttributes($node), - ); - } - throw new \LogicException("Unknown type node: " . $node->getType()); - } - - protected function parseStmt_Block(Stmt\Block $node) - { - $this->parseNodes($node->stmts, $this->block); - } - - protected function parseStmt_Expression(Stmt\Expression $node) - { - return $this->parseExprNode($node->expr); - } - - protected function parseStmt_Class(Stmt\Class_ $node) - { - $name = $this->parseTypeNode($node->namespacedName); - $old = $this->currentClass; - $this->currentClass = $name; - - $this->block->children[] = new Op\Stmt\Class_( - $name, - $node->flags, - $node->extends ? $this->parseTypeNode($node->extends) : null, - $this->parseTypeList($node->implements), - $this->parseNodes($node->stmts, new Block()), - $this->parseAttributeGroups($node->attrGroups), - $this->mapAttributes($node), - ); - $this->currentClass = $old; - } - - protected function parseStmt_ClassConst(Stmt\ClassConst $node) - { - if (! $this->currentClass instanceof Op\Type\Literal) { - throw new \RuntimeException('Unknown current class'); - } - foreach ($node->consts as $const) { - $tmp = $this->block; - $this->block = $valueBlock = new Block(); - $value = $this->parseExprNode($const->value); - $this->block = $tmp; - - $this->block->children[] = new Op\Terminal\Const_( - $this->parseExprNode($const->name), - $value, - $valueBlock, - $this->mapAttributes($node), - ); - } - } - - protected function parseStmt_ClassMethod(Stmt\ClassMethod $node) - { - if (! $this->currentClass instanceof Op\Type\Literal) { - throw new \RuntimeException('Unknown current class'); - } - - $this->script->functions[] = $func = new Func( - $node->name->toString(), - $node->flags | ($node->byRef ? Func::FLAG_RETURNS_REF : 0), - $this->parseTypeNode($node->returnType), - $this->currentClass, - ); - - if ($node->stmts !== null) { - $this->parseFunc($func, $node->params, $node->stmts, null); - } else { - $func->params = $this->parseParameterList($func, $node->params); - $func->cfg = null; - } - - $visibility = $node->flags & Modifiers::VISIBILITY_MASK; - $static = $node->flags & Modifiers::STATIC; - $final = $node->flags & Modifiers::FINAL; - $abstract = $node->flags & Modifiers::ABSTRACT; - - $this->block->children[] = $class_method = new Op\Stmt\ClassMethod( - $func, - $visibility, - (bool) $static, - (bool) $final, - (bool) $abstract, - $this->parseAttributeGroups($node->attrGroups), - $this->mapAttributes($node), - ); - $func->callableOp = $class_method; - } - - protected function parseStmt_Const(Stmt\Const_ $node) - { - foreach ($node->consts as $const) { - $tmp = $this->block; - $this->block = $valueBlock = new Block(); - $value = $this->parseExprNode($const->value); - $this->block = $tmp; - - $this->block->children[] = new Op\Terminal\Const_( - $this->parseExprNode($const->namespacedName), - $value, - $valueBlock, - $this->mapAttributes($node), - ); - } - } - - protected function parseStmt_Declare(Stmt\Declare_ $node) - { - // TODO - } - - protected function parseStmt_Do(Stmt\Do_ $node) - { - $loopBody = new Block($this->block); - $loopEnd = new Block(null, $this->block->catchTarget); - $this->block->children[] = new Jump($loopBody, $this->mapAttributes($node)); - $loopBody->addParent($this->block); - - $this->block = $loopBody; - $this->block = $this->parseNodes($node->stmts, $loopBody); - $cond = $this->readVariable($this->parseExprNode($node->cond)); - $this->block->children[] = new JumpIf($cond, $loopBody, $loopEnd, $this->mapAttributes($node)); - $this->processAssertions($cond, $loopBody, $loopEnd); - $loopBody->addParent($this->block); - $loopEnd->addParent($this->block); - - $this->block = $loopEnd; - } - - protected function parseStmt_Echo(Stmt\Echo_ $node) - { - foreach ($node->exprs as $expr) { - $this->block->children[] = new Op\Terminal\Echo_( - $this->readVariable($this->parseExprNode($expr)), - $this->mapAttributes($expr), - ); - } - } - - protected function parseStmt_For(Stmt\For_ $node) - { - $this->parseExprList($node->init, self::MODE_READ); - - $loopInit = new Block($this->block); - $loopBody = new Block(null, $this->block->catchTarget); - $loopEnd = new Block(null, $this->block->catchTarget); - - $this->block->children[] = new Jump($loopInit, $this->mapAttributes($node)); - $this->block = $loopInit; - if (! empty($node->cond)) { - $cond = $this->readVariable($this->parseExprNode($node->cond)); - } else { - $cond = new Literal(true); - } - $this->block->children[] = new JumpIf($cond, $loopBody, $loopEnd, $this->mapAttributes($node)); - $this->processAssertions($cond, $loopBody, $loopEnd); - $loopBody->addParent($this->block); - $loopEnd->addParent($this->block); - - $this->block = $this->parseNodes($node->stmts, $loopBody); - $this->parseExprList($node->loop, self::MODE_READ); - $this->block->children[] = new Jump($loopInit, $this->mapAttributes($node)); - $loopInit->addParent($this->block); - $this->block = $loopEnd; - } - - protected function parseStmt_Foreach(Stmt\Foreach_ $node) - { - $attrs = $this->mapAttributes($node); - $iterable = $this->readVariable($this->parseExprNode($node->expr)); - $this->block->children[] = new Op\Iterator\Reset($iterable, $attrs); - - $loopInit = new Block($this->block); - $loopBody = new Block(null, $this->block->catchTarget); - $loopEnd = new Block(null, $this->block->catchTarget); - - $this->block->children[] = new Jump($loopInit, $attrs); - - $loopInit->children[] = $validOp = new Op\Iterator\Valid($iterable, $attrs); - $loopInit->children[] = new JumpIf($validOp->result, $loopBody, $loopEnd, $attrs); - $this->processAssertions($validOp->result, $loopBody, $loopEnd); - $loopBody->addParent($loopInit); - $loopEnd->addParent($loopInit); - - $this->block = $loopBody; - - if ($node->keyVar) { - $this->block->children[] = $keyOp = new Op\Iterator\Key($iterable, $attrs); - $this->block->children[] = new Op\Expr\Assign($this->writeVariable($this->parseExprNode($node->keyVar)), $keyOp->result, $attrs); - } - - $this->block->children[] = $valueOp = new Op\Iterator\Value($iterable, $node->byRef, $attrs); - - if ($node->valueVar instanceof Expr\List_ || $node->valueVar instanceof Expr\Array_) { - $this->parseListAssignment($node->valueVar, $valueOp->result); - } elseif ($node->byRef) { - $this->block->children[] = new Op\Expr\AssignRef($this->writeVariable($this->parseExprNode($node->valueVar)), $valueOp->result, $attrs); - } else { - $this->block->children[] = new Op\Expr\Assign($this->writeVariable($this->parseExprNode($node->valueVar)), $valueOp->result, $attrs); - } - - $this->block = $this->parseNodes($node->stmts, $this->block); - $this->block->children[] = new Jump($loopInit, $attrs); - - $loopInit->addParent($this->block); - - $this->block = $loopEnd; - } - - protected function parseStmt_Function(Stmt\Function_ $node) - { - $this->script->functions[] = $func = new Func( - $node->namespacedName->toString(), - $node->byRef ? Func::FLAG_RETURNS_REF : 0, - $this->parseTypeNode($node->returnType), - null, - ); - $this->parseFunc($func, $node->params, $node->stmts, null); - $this->block->children[] = $function = new Op\Stmt\Function_($func, $this->parseAttributeGroups($node->attrGroups), $this->mapAttributes($node)); - $func->callableOp = $function; - } - - protected function parseStmt_Global(Stmt\Global_ $node) - { - foreach ($node->vars as $var) { - // TODO $var is not necessarily a Variable node - $this->block->children[] = new Op\Terminal\GlobalVar( - $this->writeVariable($this->parseExprNode($var->name)), - $this->mapAttributes($node), - ); - } - } - - protected function parseStmt_Goto(Stmt\Goto_ $node) - { - $attributes = $this->mapAttributes($node); - if (isset($this->ctx->labels[$node->name->toString()])) { - $labelBlock = $this->ctx->labels[$node->name->toString()]; - $this->block->children[] = new Jump($labelBlock, $attributes); - $labelBlock->addParent($this->block); - } else { - $this->ctx->unresolvedGotos[$node->name->toString()][] = [$this->block, $attributes]; - } - $this->block = new Block(null, $this->block->catchTarget); - $this->block->dead = true; - } - - protected function parseStmt_GroupUse(Stmt\GroupUse $node) - { - // ignore use statements, since names are already resolved - } - - protected function parseStmt_HaltCompiler(Stmt\HaltCompiler $node) - { - $this->block->children[] = new Op\Terminal\Echo_( - $this->readVariable(new Operand\Literal($node->remaining)), - $this->mapAttributes($node), - ); - } - - protected function parseStmt_If(Stmt\If_ $node) - { - $endBlock = new Block(null, $this->block->catchTarget); - $this->parseIf($node, $endBlock); - $this->block = $endBlock; - } - - /** - * @param Stmt\If_|Stmt\ElseIf_ $node - */ - protected function parseIf($node, Block $endBlock) - { - $attrs = $this->mapAttributes($node); - $cond = $this->readVariable($this->parseExprNode($node->cond)); - $ifBlock = new Block($this->block); - $elseBlock = new Block($this->block); - - $this->block->children[] = new JumpIf($cond, $ifBlock, $elseBlock, $attrs); - $this->processAssertions($cond, $ifBlock, $elseBlock); - - $this->block = $this->parseNodes($node->stmts, $ifBlock); - - $this->block->children[] = new Jump($endBlock, $attrs); - $endBlock->addParent($this->block); - - $this->block = $elseBlock; - - if ($node instanceof Node\Stmt\If_) { - foreach ($node->elseifs as $elseIf) { - $this->parseIf($elseIf, $endBlock); - } - if ($node->else) { - $this->block = $this->parseNodes($node->else->stmts, $this->block); - } - $this->block->children[] = new Jump($endBlock, $attrs); - $endBlock->addParent($this->block); - } - } - - protected function parseStmt_InlineHTML(Stmt\InlineHTML $node) - { - $this->block->children[] = new Op\Terminal\Echo_($this->parseExprNode($node->value), $this->mapAttributes($node)); - } - - protected function parseStmt_Interface(Stmt\Interface_ $node) - { - $name = $this->parseTypeNode($node->namespacedName); - $old = $this->currentClass; - $this->currentClass = $name; - $this->block->children[] = new Op\Stmt\Interface_( - $name, - $this->parseTypeList($node->extends), - $this->parseNodes($node->stmts, new Block()), - $this->parseAttributeGroups($node->attrGroups), - $this->mapAttributes($node), - ); - $this->currentClass = $old; - } - - protected function parseStmt_Label(Stmt\Label $node) - { - if (isset($this->ctx->labels[$node->name->toString()])) { - throw new \RuntimeException("Label '{$node->name->toString()}' already defined"); - } - - $labelBlock = new Block($this->block); - $this->block->children[] = new Jump($labelBlock, $this->mapAttributes($node)); - $labelBlock->addParent($this->block); - if (isset($this->ctx->unresolvedGotos[$node->name->toString()])) { - /** - * @var Block - * @var array $attributes - */ - foreach ($this->ctx->unresolvedGotos[$node->name->toString()] as [$block, $attributes]) { - $block->children[] = new Op\Stmt\Jump($labelBlock, $attributes); - $labelBlock->addParent($block); - } - unset($this->ctx->unresolvedGotos[$node->name->toString()]); - } - $this->block = $this->ctx->labels[$node->name->toString()] = $labelBlock; - } - - protected function parseStmt_Namespace(Stmt\Namespace_ $node) - { - $this->currentNamespace = $node->name; - $this->block = $this->parseNodes($node->stmts, $this->block); - } - - protected function parseStmt_Nop(Stmt\Nop $node) - { - // Nothing to see here, move along - } - - protected function parseStmt_Property(Stmt\Property $node) - { - $visibility = $node->flags & Modifiers::VISIBILITY_MASK; - $static = $node->flags & Modifiers::STATIC; - $readonly = $node->flags & Modifiers::READONLY; - - foreach ($node->props as $prop) { - if ($prop->default) { - $tmp = $this->block; - $this->block = $defaultBlock = new Block(); - $defaultVar = $this->parseExprNode($prop->default); - $this->block = $tmp; - } else { - $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, - $this->mapAttributes($node), - ); - } - } - - protected function parseStmt_Return(Stmt\Return_ $node) - { - $expr = null; - if ($node->expr) { - $expr = $this->readVariable($this->parseExprNode($node->expr)); - } - $this->block->children[] = new Op\Terminal\Return_($expr, $this->mapAttributes($node)); - // Dump everything after the return - $this->block = new Block($this->block); - $this->block->dead = true; - } - - protected function parseStmt_Static(Stmt\Static_ $node) - { - foreach ($node->vars as $var) { - $defaultBlock = null; - $defaultVar = null; - if ($var->default) { - $tmp = $this->block; - $this->block = $defaultBlock = new Block($this->block); - $defaultVar = $this->parseExprNode($var->default); - $this->block = $tmp; - } - $this->block->children[] = new Op\Terminal\StaticVar( - $this->writeVariable(new Operand\BoundVariable($this->parseExprNode($var->var->name), true, Operand\BoundVariable::SCOPE_FUNCTION)), - $defaultBlock, - $defaultVar, - $this->mapAttributes($node), - ); - } - } - - protected function parseStmt_Switch(Stmt\Switch_ $node) - { - if ($this->switchCanUseJumptable($node)) { - $this->compileJumptableSwitch($node); - - return; - } - - // Desugar switch into compare-and-jump sequence - $cond = $this->parseExprNode($node->cond); - $endBlock = new Block(null, $this->block->catchTarget); - $defaultBlock = $endBlock; - /** @var Block|null $prevBlock */ - $prevBlock = null; - foreach ($node->cases as $case) { - $ifBlock = new Block(null, $this->block->catchTarget); - if ($prevBlock && ! $prevBlock->dead) { - $prevBlock->children[] = new Jump($ifBlock); - $ifBlock->addParent($prevBlock); - } - - if ($case->cond) { - $caseExpr = $this->parseExprNode($case->cond); - $this->block->children[] = $cmp = new Op\Expr\BinaryOp\Equal( - $this->readVariable($cond), - $this->readVariable($caseExpr), - $this->mapAttributes($case), + switch ($node->getType()) { + case 'Name': + case 'Name_FullyQualified': + // This is safe since we always run name resolution ahead of time + return new Op\Type\Literal( + $node->name, + $this->mapAttributes($node), ); - - $elseBlock = new Block(null, $this->block->catchTarget); - $this->block->children[] = new JumpIf($cmp->result, $ifBlock, $elseBlock); - $ifBlock->addParent($this->block); - $elseBlock->addParent($this->block); - $this->block = $elseBlock; - } else { - $defaultBlock = $ifBlock; - } - - $prevBlock = $this->parseNodes($case->stmts, $ifBlock); - } - - if ($prevBlock && ! $prevBlock->dead) { - $prevBlock->children[] = new Jump($endBlock); - $endBlock->addParent($prevBlock); - } - - $this->block->children[] = new Jump($defaultBlock); - $defaultBlock->addParent($this->block); - $this->block = $endBlock; - } - - protected function parseStmt_Trait(Stmt\Trait_ $node) - { - $name = $this->parseTypeNode($node->namespacedName); - $old = $this->currentClass; - $this->currentClass = $name; - $this->block->children[] = new Op\Stmt\Trait_( - $name, - $this->parseNodes($node->stmts, new Block()), - $this->parseAttributeGroups($node->attrGroups), - $this->mapAttributes($node), - ); - $this->currentClass = $old; - } - - protected function parseStmt_TraitUse(Stmt\TraitUse $node) - { - $traits = []; - $adaptations = []; - foreach ($node->traits as $trait_) { - $traits[] = new Literal($trait_->toCodeString()); - } - foreach ($node->adaptations as $adaptation) { - if ($adaptation instanceof Stmt\TraitUseAdaptation\Alias) { - $adaptations[] = new Alias( - $adaptation->trait != null ? new Literal($adaptation->trait->toCodeString()) : null, - new Literal($adaptation->method->name), - $adaptation->newName != null ? new Literal($adaptation->newName->name) : null, - $adaptation->newModifier, - $this->mapAttributes($adaptation), + case 'NullableType': + return new Op\Type\Nullable( + $this->parseTypeNode($node->type), + $this->mapAttributes($node), ); - } elseif ($adaptation instanceof Stmt\TraitUseAdaptation\Precedence) { - $insteadofs = []; - foreach ($adaptation->insteadof as $insteadof) { - $insteadofs[] = new Literal($insteadof->toCodeString()); - } - $adaptations[] = new Precedence( - $adaptation->trait != null ? new Literal($adaptation->trait->toCodeString()) : null, - new Literal($adaptation->method->name), - $insteadofs, - $this->mapAttributes($adaptation), + case 'UnionType': + return new Op\Type\Union( + $this->parseTypeList(...$node->types), + $this->mapAttributes($node), + ); + case 'IntersectionType': + return new Op\Type\Intersection( + $this->parseTypeList(...$node->types), + $this->mapAttributes($node), + ); + case 'Identifier': + return new Op\Type\Literal( + $node->name, + $this->mapAttributes($node), ); - } - } - $this->block->children[] = new TraitUse($traits, $adaptations, $this->mapAttributes($node)); - } - - protected function parseStmt_TryCatch(Stmt\TryCatch $node) - { - $finally = new Block(); - $catchTarget = new CatchTarget($finally); - $finallyTarget = new CatchTarget($finally); - $body = new Block($this->block, $catchTarget); - $finally->addParent($body); - $finally->setCatchTarget($this->block->catchTarget); - $next = new Block($finally); - - foreach ($node->catches as $catch) { - if ($catch->var) { - $var = $this->writeVariable($this->parseExprNode($catch->var)); - } else { - $var = new Operand\NullOperand(); - } - - $catchBody = new Block($body, $finallyTarget); - $finally->addParent($catchBody); - $catchBody2 = $this->parseNodes($catch->stmts, $catchBody); - $catchBody2->children[] = new Jump($finally); - - $parsedTypes = []; - foreach ($catch->types as $type) { - $parsedTypes[] = $this->parseTypeNode($type); - } - - $type = new Op\Type\Union( - $parsedTypes, - $this->mapAttributes($catch), - ); - - $catchTarget->addCatch($type, $var, $catchBody); - } - - // parsing body stmts is done after the catches because we want - // to add catch blocks (and finally blocks) as parents of any subblock of the body - $next2 = $this->parseNodes($node->stmts, $body); - $next2->children[] = new Jump($finally); - - if ($node->finally != null) { - $nf = $this->parseNodes($node->finally->stmts, $finally); - $nf->children[] = new Jump($next); - } else { - $finally->children[] = new Jump($next); } - - $this->block->children[] = new Try_($body, $catchTarget->catches, $finally, $this->mapAttributes($node)); - $this->block = $next; - } - - protected function parseStmt_Unset(Stmt\Unset_ $node) - { - $this->block->children[] = new Op\Terminal\Unset_( - $this->parseExprList($node->vars, self::MODE_WRITE), - $this->mapAttributes($node), - ); - } - - protected function parseStmt_Use(Stmt\Use_ $node) - { - // ignore use statements, since names are already resolved - } - - protected function parseStmt_While(Stmt\While_ $node) - { - $loopInit = new Block(null, $this->block->catchTarget); - $loopBody = new Block(null, $this->block->catchTarget); - $loopEnd = new Block(null, $this->block->catchTarget); - $this->block->children[] = new Jump($loopInit, $this->mapAttributes($node)); - $loopInit->addParent($this->block); - $this->block = $loopInit; - $cond = $this->readVariable($this->parseExprNode($node->cond)); - - $this->block->children[] = new JumpIf($cond, $loopBody, $loopEnd, $this->mapAttributes($node)); - $this->processAssertions($cond, $loopBody, $loopEnd); - $loopBody->addParent($this->block); - $loopEnd->addParent($this->block); - - $this->block = $this->parseNodes($node->stmts, $loopBody); - $this->block->children[] = new Jump($loopInit, $this->mapAttributes($node)); - $loopInit->addParent($this->block); - $this->block = $loopEnd; + throw new LogicException("Unknown type node: " . $node->getType()); } /** @@ -828,7 +264,7 @@ protected function parseStmt_While(Stmt\While_ $node) * * @return Operand[] */ - protected function parseExprList(array $expr, $readWrite = self::MODE_NONE): array + public function parseExprList(array $expr, $readWrite = self::MODE_NONE): array { $vars = array_map([$this, 'parseExprNode'], $expr); if ($readWrite === self::MODE_READ) { @@ -836,48 +272,27 @@ protected function parseExprList(array $expr, $readWrite = self::MODE_NONE): arr } elseif ($readWrite === self::MODE_WRITE) { $vars = array_map([$this, 'writeVariable'], $vars); } - return $vars; } - protected function parseExprNode($expr) + public function parseExprNode($expr): ?Operand { if (null === $expr) { - return; + return null; } if (is_scalar($expr)) { return new Literal($expr); } if (is_array($expr)) { $list = $this->parseExprList($expr); - return end($list); } + if ($expr instanceof Node\Arg) { + return $this->readVariable($this->parseExprNode($expr->value)); + } if ($expr instanceof Node\Identifier) { return new Literal($expr->name); } - if ($expr instanceof Node\Expr\Variable) { - if (is_scalar($expr->name)) { - if ($expr->name === 'this') { - return new Operand\BoundVariable( - $this->parseExprNode($expr->name), - false, - Operand\BoundVariable::SCOPE_OBJECT, - $this->currentClass, - ); - } - - return new Variable($this->parseExprNode($expr->name)); - } - - // variable variable - $this->block->children[] = $op = new Op\Expr\VarVar( - $this->readVariable($this->parseExprNode($expr->name)), - $this->mapAttributes($expr) - ); - - return $op->result; - } if ($expr instanceof Node\Name) { $isReserved = in_array(strtolower($expr->getLast()), ['int', 'string', 'array', 'callable', 'float', 'bool'], true); if ($isReserved) { @@ -887,788 +302,44 @@ protected function parseExprNode($expr) return new Literal($expr->toString()); } - if ($expr instanceof Node\Scalar) { - return $this->parseScalarNode($expr); - } + if ($expr instanceof Node\InterpolatedStringPart) { return new Literal($expr->value); } - if ($expr instanceof Node\Expr\AssignOp) { - $var = $this->parseExprNode($expr->var); - $read = $this->readVariable($var); - $write = $this->writeVariable($var); - $e = $this->readVariable($this->parseExprNode($expr->expr)); - $class = [ - 'Expr_AssignOp_BitwiseAnd' => Op\Expr\BinaryOp\BitwiseAnd::class, - 'Expr_AssignOp_BitwiseOr' => Op\Expr\BinaryOp\BitwiseOr::class, - 'Expr_AssignOp_BitwiseXor' => Op\Expr\BinaryOp\BitwiseXor::class, - 'Expr_AssignOp_Coalesce' => Op\Expr\BinaryOp\Coalesce::class, - 'Expr_AssignOp_Concat' => Op\Expr\BinaryOp\Concat::class, - 'Expr_AssignOp_Div' => Op\Expr\BinaryOp\Div::class, - 'Expr_AssignOp_Minus' => Op\Expr\BinaryOp\Minus::class, - 'Expr_AssignOp_Mod' => Op\Expr\BinaryOp\Mod::class, - 'Expr_AssignOp_Mul' => Op\Expr\BinaryOp\Mul::class, - 'Expr_AssignOp_Plus' => Op\Expr\BinaryOp\Plus::class, - 'Expr_AssignOp_Pow' => Op\Expr\BinaryOp\Pow::class, - 'Expr_AssignOp_ShiftLeft' => Op\Expr\BinaryOp\ShiftLeft::class, - 'Expr_AssignOp_ShiftRight' => Op\Expr\BinaryOp\ShiftRight::class, - ][$expr->getType()]; - if (empty($class)) { - throw new \RuntimeException('AssignOp Not Found: ' . $expr->getType()); - } - $attrs = $this->mapAttributes($expr); - $this->block->children[] = $op = new $class($read, $e, $attrs); - $this->block->children[] = new Op\Expr\Assign($write, $op->result, $attrs); - - return $op->result; - } - if ($expr instanceof Node\Expr\BinaryOp) { - if ($expr instanceof AstBinaryOp\LogicalAnd || $expr instanceof AstBinaryOp\BooleanAnd) { - return $this->parseShortCircuiting($expr, false); - } - if ($expr instanceof AstBinaryOp\LogicalOr || $expr instanceof AstBinaryOp\BooleanOr) { - return $this->parseShortCircuiting($expr, true); - } - - $left = $this->readVariable($this->parseExprNode($expr->left)); - $right = $this->readVariable($this->parseExprNode($expr->right)); - $class = [ - 'Expr_BinaryOp_BitwiseAnd' => Op\Expr\BinaryOp\BitwiseAnd::class, - 'Expr_BinaryOp_BitwiseOr' => Op\Expr\BinaryOp\BitwiseOr::class, - 'Expr_BinaryOp_BitwiseXor' => Op\Expr\BinaryOp\BitwiseXor::class, - 'Expr_BinaryOp_Coalesce' => Op\Expr\BinaryOp\Coalesce::class, - 'Expr_BinaryOp_Concat' => Op\Expr\BinaryOp\Concat::class, - 'Expr_BinaryOp_Div' => Op\Expr\BinaryOp\Div::class, - 'Expr_BinaryOp_Equal' => Op\Expr\BinaryOp\Equal::class, - 'Expr_BinaryOp_Greater' => Op\Expr\BinaryOp\Greater::class, - 'Expr_BinaryOp_GreaterOrEqual' => Op\Expr\BinaryOp\GreaterOrEqual::class, - 'Expr_BinaryOp_Identical' => Op\Expr\BinaryOp\Identical::class, - 'Expr_BinaryOp_LogicalXor' => Op\Expr\BinaryOp\LogicalXor::class, - 'Expr_BinaryOp_Minus' => Op\Expr\BinaryOp\Minus::class, - 'Expr_BinaryOp_Mod' => Op\Expr\BinaryOp\Mod::class, - 'Expr_BinaryOp_Mul' => Op\Expr\BinaryOp\Mul::class, - 'Expr_BinaryOp_NotEqual' => Op\Expr\BinaryOp\NotEqual::class, - 'Expr_BinaryOp_NotIdentical' => Op\Expr\BinaryOp\NotIdentical::class, - 'Expr_BinaryOp_Plus' => Op\Expr\BinaryOp\Plus::class, - 'Expr_BinaryOp_Pow' => Op\Expr\BinaryOp\Pow::class, - 'Expr_BinaryOp_ShiftLeft' => Op\Expr\BinaryOp\ShiftLeft::class, - 'Expr_BinaryOp_ShiftRight' => Op\Expr\BinaryOp\ShiftRight::class, - 'Expr_BinaryOp_Smaller' => Op\Expr\BinaryOp\Smaller::class, - 'Expr_BinaryOp_SmallerOrEqual' => Op\Expr\BinaryOp\SmallerOrEqual::class, - 'Expr_BinaryOp_Spaceship' => Op\Expr\BinaryOp\Spaceship::class, - ][$expr->getType()]; - if (empty($class)) { - throw new \RuntimeException('BinaryOp Not Found: ' . $expr->getType()); - } - $this->block->children[] = $op = new $class($left, $right, $this->mapAttributes($expr)); - - return $op->result; - } - if ($expr instanceof Node\Expr\Cast) { - $e = $this->readVariable($this->parseExprNode($expr->expr)); - $class = [ - 'Expr_Cast_Array' => Op\Expr\Cast\Array_::class, - 'Expr_Cast_Bool' => Op\Expr\Cast\Bool_::class, - 'Expr_Cast_Double' => Op\Expr\Cast\Double::class, - 'Expr_Cast_Int' => Op\Expr\Cast\Int_::class, - 'Expr_Cast_Object' => Op\Expr\Cast\Object_::class, - 'Expr_Cast_String' => Op\Expr\Cast\String_::class, - 'Expr_Cast_Unset' => Op\Expr\Cast\Unset_::class, - - ][$expr->getType()]; - if (empty($class)) { - throw new \RuntimeException('Cast Not Found: ' . $expr->getType()); - } - $this->block->children[] = $op = new $class($e, $this->mapAttributes($expr)); - - return $op->result; - } - - $method = 'parse' . $expr->getType(); - if (method_exists($this, $method)) { - $op = $this->{$method}($expr); - if ($op instanceof Op) { - $this->block->children[] = $op; - - return $op->result; - } - if ($op instanceof Operand) { - return $op; - } - } else { - throw new \RuntimeException('Unknown Expr Type ' . $expr->getType()); - } - - throw new \RuntimeException('Invalid state, should never happen'); - } - - 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 = []; - $values = []; - $byRef = []; - if ($expr->items) { - foreach ($expr->items as $item) { - if ($item->key) { - $keys[] = $this->readVariable($this->parseExprNode($item->key)); - } else { - $keys[] = new Operand\NullOperand(); - } - $values[] = $this->readVariable($this->parseExprNode($item->value)); - $byRef[] = $item->byRef; - } - } - - return new Op\Expr\Array_($keys, $values, $byRef, $this->mapAttributes($expr)); - } - - protected function parseExpr_ArrayDimFetch(Expr\ArrayDimFetch $expr) - { - $v = $this->readVariable($this->parseExprNode($expr->var)); - if (null !== $expr->dim) { - $d = $this->readVariable($this->parseExprNode($expr->dim)); - } else { - $d = new Operand\NullOperand(); - } - - return new Op\Expr\ArrayDimFetch($v, $d, $this->mapAttributes($expr)); - } - - protected function parseExpr_Assign(Expr\Assign $expr) - { - $e = $this->readVariable($this->parseExprNode($expr->expr)); - if ($expr->var instanceof Expr\List_ || $expr->var instanceof Expr\Array_) { - $this->parseListAssignment($expr->var, $e); - - return $e; - } - $v = $this->writeVariable($this->parseExprNode($expr->var)); - - return new Op\Expr\Assign($v, $e, $this->mapAttributes($expr)); - } - - protected function parseExpr_AssignRef(Expr\AssignRef $expr) - { - $e = $this->readVariable($this->parseExprNode($expr->expr)); - $v = $this->writeVariable($this->parseExprNode($expr->var)); - - return new Op\Expr\AssignRef($v, $e, $this->mapAttributes($expr)); - } - - protected function parseExpr_BitwiseNot(Expr\BitwiseNot $expr) - { - return new Op\Expr\BitwiseNot( - $this->readVariable($this->parseExprNode($expr->expr)), - $this->mapAttributes($expr), - ); - } - - protected function parseExpr_BooleanNot(Expr\BooleanNot $expr) - { - $cond = $this->readVariable($this->parseExprNode($expr->expr)); - $op = new Op\Expr\BooleanNot($cond, $this->mapAttributes($expr)); - foreach ($cond->assertions as $assertion) { - $op->result->addAssertion($assertion['var'], new Assertion\NegatedAssertion([$assertion['assertion']])); - } - - return $op; - } - - protected function parseExpr_Closure(Expr\Closure $expr) - { - $uses = []; - foreach ($expr->uses as $use) { - $uses[] = new Operand\BoundVariable( - $this->readVariable(new Literal($use->var->name)), - $use->byRef, - Operand\BoundVariable::SCOPE_LOCAL, - ); - } - - $flags = Func::FLAG_CLOSURE; - $flags |= $expr->byRef ? Func::FLAG_RETURNS_REF : 0; - $flags |= $expr->static ? Func::FLAG_STATIC : 0; - - $this->script->functions[] = $func = new Func( - '{anonymous}#' . ++$this->anonId, - $flags, - $this->parseTypeNode($expr->returnType), - null, - ); - $this->parseFunc($func, $expr->params, $expr->stmts, null); - - $closure = new Op\Expr\Closure($func, $uses, $this->mapAttributes($expr)); - $func->callableOp = $closure; - - return $closure; - } - - protected function parseExpr_ArrowFunction(Expr\ArrowFunction $expr) - { - $flags = Func::FLAG_CLOSURE; - $flags |= $expr->byRef ? Func::FLAG_RETURNS_REF : 0; - $flags |= $expr->static ? Func::FLAG_STATIC : 0; - - $this->script->functions[] = $func = new Func( - '{anonymous}#' . ++$this->anonId, - $flags, - $this->parseTypeNode($expr->returnType), - null, - ); - $stmts = [ - new Stmt\Return_($expr->expr), - ]; - $this->parseFunc($func, $expr->params, $stmts); - - $closure = new Op\Expr\ArrowFunction($func, $this->mapAttributes($expr)); - $func->callableOp = $closure; - - return $closure; - } - - protected function parseExpr_ClassConstFetch(Expr\ClassConstFetch $expr) - { - $c = $this->readVariable($this->parseExprNode($expr->class)); - $n = $this->readVariable($this->parseExprNode($expr->name)); - - return new Op\Expr\ClassConstFetch($c, $n, $this->mapAttributes($expr)); - } - - protected function parseExpr_Clone(Expr\Clone_ $expr) - { - return new Op\Expr\Clone_($this->readVariable($this->parseExprNode($expr->expr)), $this->mapAttributes($expr)); - } - - protected function parseExpr_ConstFetch(Expr\ConstFetch $expr) - { - if ($expr->name->isUnqualified()) { - $lcname = strtolower($expr->name->toString()); - switch ($lcname) { - case 'null': - return new Literal(null); - case 'true': - return new Literal(true); - case 'false': - return new Literal(false); - } - } - - $nsName = null; - if ($this->currentNamespace && $expr->name->isUnqualified()) { - $nsName = $this->parseExprNode(Node\Name::concat($this->currentNamespace, $expr->name)); - } - - return new Op\Expr\ConstFetch($this->parseExprNode($expr->name), $nsName, $this->mapAttributes($expr)); - } - - protected function parseExpr_Empty(Expr\Empty_ $expr) - { - return new Op\Expr\Empty_($this->readVariable($this->parseExprNode($expr->expr)), $this->mapAttributes($expr)); - } - - protected function parseExpr_ErrorSuppress(Expr\ErrorSuppress $expr) - { - $attrs = $this->mapAttributes($expr); - $block = new ErrorSuppressBlock(); - $this->block->children[] = new Jump($block, $attrs); - $block->addParent($this->block); - $this->block = $block; - $result = $this->parseExprNode($expr->expr); - $end = new Block($this->block); - $this->block->children[] = new Jump($end, $attrs); - $this->block = $end; - - return $result; - } - - protected function parseExpr_Eval(Expr\Eval_ $expr) - { - return new Op\Expr\Eval_($this->readVariable($this->parseExprNode($expr->expr)), $this->mapAttributes($expr)); - } - - protected function parseExpr_Throw(Expr\Throw_ $expr) - { - $this->block->children[] = new Op\Terminal\Throw_( - $this->readVariable($this->parseExprNode($expr->expr)), - $this->mapAttributes($expr) - ); - - // Dump everything after the throw - $this->block = new Block(null, $this->block->catchTarget); - $this->block->dead = true; - - return new Literal(1); - } - - protected function parseExpr_Exit(Expr\Exit_ $expr) - { - $e = null; - if ($expr->expr) { - $e = $this->readVariable($this->parseExprNode($expr->expr)); - } - - $this->block->children[] = new Op\Terminal\Exit_($e, $this->mapAttributes($expr)); - // Dump everything after the exit - $this->block = new Block(null, $this->block->catchTarget); - $this->block->dead = true; - - return new Literal(1); - } - - protected function parseExpr_FuncCall(Expr\FuncCall $expr) - { - $args = $this->parseExprList($expr->args, self::MODE_READ); - $name = $this->readVariable($this->parseExprNode($expr->name)); - if ($this->currentNamespace && $expr->name instanceof Node\Name && $expr->name->isUnqualified()) { - $op = new Op\Expr\NsFuncCall( - $name, - $this->parseExprNode(Node\Name::concat($this->currentNamespace, $expr->name)), - $args, - $this->mapAttributes($expr), - ); - } else { - $op = new Op\Expr\FuncCall($name, $args, $this->mapAttributes($expr)); - } - if ($name instanceof Operand\Literal) { - static $assertionFunctions = [ - 'is_array' => 'array', - 'is_bool' => 'bool', - 'is_callable' => 'callable', - 'is_double' => 'float', - 'is_float' => 'float', - 'is_int' => 'int', - 'is_integer' => 'int', - 'is_long' => 'int', - 'is_null' => 'null', - 'is_numeric' => 'numeric', - 'is_object' => 'object', - 'is_real' => 'float', - 'is_string' => 'string', - 'is_resource' => 'resource', - ]; - $lname = strtolower($name->value); - if (isset($assertionFunctions[$lname])) { - $op->result->addAssertion( - $args[0], - new Assertion\TypeAssertion(new Operand\Literal($assertionFunctions[$lname])), - ); - } + if (isset($this->exprHandlers[$expr->getType()])) { + return $this->exprHandlers[$expr->getType()]->handleExpr($expr); } - - return $op; + var_dump(array_keys($this->exprHandlers)); + throw new RuntimeException('Unknown Expr Type ' . $expr->getType()); } - protected function parseExpr_Include(Expr\Include_ $expr) + public function parseAttributes(Node\Attribute ...$attrs) { - return new Op\Expr\Include_($this->readVariable($this->parseExprNode($expr->expr)), $expr->type, $this->mapAttributes($expr)); + return array_map(fn($attr) => new Op\Attributes\Attribute( + $this->readVariable($this->parseExprNode($attr->name)), + $this->parseExprList($attr->args), + $this->mapAttributes($attr) + ), $attrs); } - protected function parseExpr_Instanceof(Expr\Instanceof_ $expr) + public function parseAttributeGroups(Node\AttributeGroup ...$attrGroups) { - $var = $this->readVariable($this->parseExprNode($expr->expr)); - $class = $this->readVariable($this->parseExprNode($expr->class)); - $op = new Op\Expr\InstanceOf_( - $var, - $class, - $this->mapAttributes($expr), - ); - $op->result->addAssertion($var, new Assertion\TypeAssertion($class)); - - return $op; + return array_map(fn($attrGroup) => new Op\Attributes\AttributeGroup( + $this->parseAttributes(...$attrGroup->attrs), + $this->mapAttributes($attrGroup) + ), $attrGroups); } - protected function parseExpr_Isset(Expr\Isset_ $expr) - { - return new Op\Expr\Isset_( - $this->parseExprList($expr->vars, self::MODE_READ), - $this->mapAttributes($expr), - ); - } - - /** - * @param Expr\List_|Expr\Array_ $expr - */ - protected function parseListAssignment($expr, Operand $rhs) - { - $attributes = $this->mapAttributes($expr); - foreach ($expr->items as $i => $item) { - if (null === $item) { - continue; - } - - if ($item->key === null) { - $key = new Operand\Literal($i); - } else { - $key = $this->readVariable($this->parseExprNode($item->key)); - } - - $var = $item->value; - $fetch = new Op\Expr\ArrayDimFetch($rhs, $key, $attributes); - $this->block->children[] = $fetch; - if ($var instanceof Expr\List_ || $var instanceof Expr\Array_) { - $this->parseListAssignment($var, $fetch->result); - - continue; - } - - $assign = new Op\Expr\Assign( - $this->writeVariable($this->parseExprNode($var)), - $fetch->result, - $attributes, - ); - $this->block->children[] = $assign; - } - } - - protected function parseExpr_MethodCall(Expr\MethodCall $expr) - { - return new Op\Expr\MethodCall( - $this->readVariable($this->parseExprNode($expr->var)), - $this->readVariable($this->parseExprNode($expr->name)), - $this->parseExprList($expr->args, self::MODE_READ), - $this->mapAttributes($expr), - ); - } - - protected function parseExpr_New(Expr\New_ $expr) - { - if ($expr->class instanceof Node\Stmt\Class_) { - $this->parseStmt_Class($expr->class); - $classExpr = $expr->class->name; - } else { - $classExpr = $expr->class; - } - - return new Op\Expr\New_( - $this->readVariable($this->parseExprNode($classExpr)), - $this->parseExprList($expr->args, self::MODE_READ), - $this->mapAttributes($expr), - ); - } - - protected function parseExpr_PostDec(Expr\PostDec $expr) - { - $var = $this->parseExprNode($expr->var); - $read = $this->readVariable($var); - $write = $this->writeVariable($var); - $this->block->children[] = $op = new Op\Expr\BinaryOp\Minus($read, new Operand\Literal(1), $this->mapAttributes($expr)); - $this->block->children[] = new Op\Expr\Assign($write, $op->result, $this->mapAttributes($expr)); - - return $read; - } - - protected function parseExpr_PostInc(Expr\PostInc $expr) - { - $var = $this->parseExprNode($expr->var); - $read = $this->readVariable($var); - $write = $this->writeVariable($var); - $this->block->children[] = $op = new Op\Expr\BinaryOp\Plus($read, new Operand\Literal(1), $this->mapAttributes($expr)); - $this->block->children[] = new Op\Expr\Assign($write, $op->result, $this->mapAttributes($expr)); - - return $read; - } - - protected function parseExpr_PreDec(Expr\PreDec $expr) - { - $var = $this->parseExprNode($expr->var); - $read = $this->readVariable($var); - $write = $this->writeVariable($var); - $this->block->children[] = $op = new Op\Expr\BinaryOp\Minus($read, new Operand\Literal(1), $this->mapAttributes($expr)); - $this->block->children[] = new Op\Expr\Assign($write, $op->result, $this->mapAttributes($expr)); - - return $op->result; - } - - protected function parseExpr_PreInc(Expr\PreInc $expr) - { - $var = $this->parseExprNode($expr->var); - $read = $this->readVariable($var); - $write = $this->writeVariable($var); - $this->block->children[] = $op = new Op\Expr\BinaryOp\Plus($read, new Operand\Literal(1), $this->mapAttributes($expr)); - $this->block->children[] = new Op\Expr\Assign($write, $op->result, $this->mapAttributes($expr)); - - return $op->result; - } - - protected function parseExpr_Print(Expr\Print_ $expr) - { - return new Op\Expr\Print_($this->readVariable($this->parseExprNode($expr->expr)), $this->mapAttributes($expr)); - } - - protected function parseExpr_PropertyFetch(Expr\PropertyFetch $expr) - { - return new Op\Expr\PropertyFetch( - $this->readVariable($this->parseExprNode($expr->var)), - $this->readVariable($this->parseExprNode($expr->name)), - $this->mapAttributes($expr), - ); - } - - protected function parseExpr_StaticCall(Expr\StaticCall $expr) - { - return new Op\Expr\StaticCall( - $this->readVariable($this->parseExprNode($expr->class)), - $this->readVariable($this->parseExprNode($expr->name)), - $this->parseExprList($expr->args, self::MODE_READ), - $this->mapAttributes($expr), - ); - } - - protected function parseExpr_StaticPropertyFetch(Expr\StaticPropertyFetch $expr) - { - return new Op\Expr\StaticPropertyFetch( - $this->readVariable($this->parseExprNode($expr->class)), - $this->readVariable($this->parseExprNode($expr->name)), - $this->mapAttributes($expr), - ); - } - - protected function parseExpr_Ternary(Expr\Ternary $expr) - { - $attrs = $this->mapAttributes($expr); - $cond = $this->readVariable($this->parseExprNode($expr->cond)); - - $ifBlock = new Block(); - $elseBlock = new Block(); - $endBlock = new Block(); - - $this->block->children[] = new JumpIf($cond, $ifBlock, $elseBlock, $attrs); - $this->processAssertions($cond, $ifBlock, $elseBlock); - $ifBlock->addParent($this->block); - $elseBlock->addParent($this->block); - - $this->block = $ifBlock; - $ifVar = new Temporary(); - if ($expr->if) { - $this->block->children[] = new Op\Expr\Assign( - $ifVar, - $this->readVariable($this->parseExprNode($expr->if)), - $attrs, - ); - } else { - $this->block->children[] = new Op\Expr\Assign($ifVar, $cond, $attrs); - } - $this->block->children[] = new Jump($endBlock, $attrs); - $endBlock->addParent($this->block); - - $this->block = $elseBlock; - $elseVar = new Temporary(); - $this->block->children[] = new Op\Expr\Assign( - $elseVar, - $this->readVariable($this->parseExprNode($expr->else)), - $attrs, - ); - $this->block->children[] = new Jump($endBlock, $attrs); - $endBlock->addParent($this->block); - - $this->block = $endBlock; - $result = new Temporary(); - $phi = new Op\Phi($result, ['block' => $this->block]); - $phi->addOperand($ifVar); - $phi->addOperand($elseVar); - $this->block->phi[] = $phi; - - return $result; - } - - protected function parseExpr_UnaryMinus(Expr\UnaryMinus $expr) - { - return new Op\Expr\UnaryMinus($this->readVariable($this->parseExprNode($expr->expr)), $this->mapAttributes($expr)); - } - - protected function parseExpr_UnaryPlus(Expr\UnaryPlus $expr) - { - return new Op\Expr\UnaryPlus($this->readVariable($this->parseExprNode($expr->expr)), $this->mapAttributes($expr)); - } - - protected function parseExpr_Yield(Expr\Yield_ $expr) - { - $key = null; - $value = null; - if ($expr->key) { - $key = $this->readVariable($this->parseExprNode($expr->key)); - } - if ($expr->value) { - $key = $this->readVariable($this->parseExprNode($expr->value)); - } - - return new Op\Expr\Yield_($value, $key, $this->mapAttributes($expr)); - } - - protected function parseExpr_ShellExec(Expr\ShellExec $expr) - { - $this->block->children[] = $arg = new Op\Expr\ConcatList( - $this->parseExprList($expr->parts, self::MODE_READ), - $this->mapAttributes($expr), - ); - - return new Op\Expr\FuncCall( - new Operand\Literal('shell_exec'), - [$arg->result], - $this->mapAttributes($expr), - ); - } - - protected function processAssertions(Operand $op, Block $if, Block $else) - { - $block = $this->block; - foreach ($op->assertions as $assert) { - $this->block = $if; - array_unshift($this->block->children, new Op\Expr\Assertion( - $this->readVariable($assert['var']), - $this->writeVariable($assert['var']), - $this->readAssertion($assert['assertion']), - )); - $this->block = $else; - array_unshift($this->block->children, new Op\Expr\Assertion( - $this->readVariable($assert['var']), - $this->writeVariable($assert['var']), - new Assertion\NegatedAssertion([$this->readAssertion($assert['assertion'])]), - )); - } - $this->block = $block; - } - - protected function readAssertion(Assertion $assert) + public function readAssertion(Assertion $assert): Assertion { if ($assert->value instanceof Operand) { return new $assert($this->readVariable($assert->value)); } - $vars = []; - foreach ($assert->value as $child) { - $vars[] = $this->readAssertion($child); - } - - return new $assert($vars, $assert->mode); + return new $assert(array_map(fn($child) => $this->readAssertion($child), $assert->value), $assert->mode); } - protected function throwUndefinedLabelError() - { - foreach ($this->ctx->unresolvedGotos as $name => $_) { - throw new \RuntimeException("'goto' to undefined label '{$name}'"); - } - } - - private function switchCanUseJumptable(Stmt\Switch_ $node) - { - foreach ($node->cases as $case) { - if ( - null !== $case->cond - && ! $case->cond instanceof Node\Scalar\LNumber - && ! $case->cond instanceof Node\Scalar\String_ - ) { - return false; - } - } - - return true; - } - - private function compileJumptableSwitch(Stmt\Switch_ $node) - { - $cond = $this->readVariable($this->parseExprNode($node->cond)); - $cases = []; - $targets = []; - $endBlock = new Block(null, $this->block->catchTarget); - $defaultBlock = $endBlock; - /** @var null|Block $block */ - $block = null; - foreach ($node->cases as $case) { - $caseBlock = new Block($this->block); - if ($block && ! $block->dead) { - // wire up! - $block->children[] = new Jump($caseBlock); - $caseBlock->addParent($block); - } - - if ($case->cond) { - $targets[] = $caseBlock; - $cases[] = $this->parseExprNode($case->cond); - } else { - $defaultBlock = $caseBlock; - } - - $block = $this->parseNodes($case->stmts, $caseBlock); - } - $this->block->children[] = new Op\Stmt\Switch_( - $cond, - $cases, - $targets, - $defaultBlock, - $this->mapAttributes($node), - ); - if ($block && ! $block->dead) { - // wire end of block to endblock - $block->children[] = new Jump($endBlock); - $endBlock->addParent($block); - } - $this->block = $endBlock; - } - - private function parseScalarNode(Node\Scalar $scalar) - { - switch ($scalar->getType()) { - case 'Scalar_InterpolatedString': - case 'Scalar_Encapsed': - $op = new Op\Expr\ConcatList($this->parseExprList($scalar->parts, self::MODE_READ), $this->mapAttributes($scalar)); - $this->block->children[] = $op; - - return $op->result; - case 'Scalar_Float': - case 'Scalar_Int': - case 'Scalar_LNumber': - case 'Scalar_String': - case 'Scalar_InterpolatedStringPart': - case 'Scalar_EncapsedStringPart': - return new Literal($scalar->value); - case 'Scalar_MagicConst_Class': - // TODO - return new Literal('__CLASS__'); - case 'Scalar_MagicConst_Dir': - return new Literal(dirname($this->fileName)); - case 'Scalar_MagicConst_File': - return new Literal($this->fileName); - case 'Scalar_MagicConst_Namespace': - // TODO - return new Literal('__NAMESPACE__'); - case 'Scalar_MagicConst_Method': - // TODO - return new Literal('__METHOD__'); - case 'Scalar_MagicConst_Function': - // TODO - return new Literal('__FUNCTION__'); - default: - var_dump($scalar); - throw new \RuntimeException('Unknown how to deal with scalar type ' . $scalar->getType()); - } - } - - private function parseParameterList(Func $func, array $params) + public function parseParameterList(Func $func, array $params): array { if (empty($params)) { return []; @@ -1689,57 +360,19 @@ private function parseParameterList(Func $func, array $params) $this->parseTypeNode($param->type), $param->byRef, $param->variadic, - $this->parseAttributeGroups($param->attrGroups), + $this->parseAttributeGroups(...$param->attrGroups), $defaultVar, $defaultBlock, $this->mapAttributes($param), ); - $p->result->original = new Operand\Variable(new Operand\Literal($p->name->value)); + $p->result->original = new Variable(new Literal($p->name->value)); $p->function = $func; } return $result; } - private function parseShortCircuiting(AstBinaryOp $expr, $isOr) - { - $result = new Temporary(); - $longBlock = new Block(null, $this->block->catchTarget); - $endBlock = new Block(null, $this->block->catchTarget); - - $left = $this->readVariable($this->parseExprNode($expr->left)); - $if = $isOr ? $endBlock : $longBlock; - $else = $isOr ? $longBlock : $endBlock; - - $this->block->children[] = new JumpIf($left, $if, $else); - $longBlock->addParent($this->block); - $endBlock->addParent($this->block); - - $this->block = $longBlock; - $right = $this->readVariable($this->parseExprNode($expr->right)); - $boolCast = new Op\Expr\Cast\Bool_($right); - $this->block->children[] = $boolCast; - $this->block->children[] = new Jump($endBlock); - $endBlock->addParent($this->block); - - $this->block = $endBlock; - $phi = new Op\Phi($result, ['block' => $this->block]); - $phi->addOperand(new Literal($isOr)); - $phi->addOperand($boolCast->result); - $this->block->phi[] = $phi; - - $mode = $isOr ? Assertion::MODE_UNION : Assertion::MODE_INTERSECTION; - foreach ($left->assertions as $assert) { - $result->addAssertion($assert['var'], $assert['assertion'], $mode); - } - foreach ($right->assertions as $assert) { - $result->addAssertion($assert['var'], $assert['assertion'], $mode); - } - - return $result; - } - - private function mapAttributes(Node $expr) + public function mapAttributes(Node $expr): array { return array_merge( [ @@ -1749,35 +382,35 @@ private function mapAttributes(Node $expr) ); } - private function readVariable(Operand $var) + public function readVariable(Operand $var): Operand { if ($var instanceof Operand\BoundVariable) { // bound variables are immune to SSA return $var; } - if ($var instanceof Operand\Variable) { + if ($var instanceof Variable) { if ($var->name instanceof Literal) { return $this->readVariableName($this->getVariableName($var), $this->block); } $this->readVariable($var->name); // variable variable read - all we can do is register the nested read return $var; } - if ($var instanceof Operand\Temporary && $var->original instanceof Operand) { + if ($var instanceof Temporary && $var->original instanceof Operand) { return $this->readVariable($var->original); } return $var; } - private function writeVariable(Operand $var) + public function writeVariable(Operand $var): Operand { - while ($var instanceof Operand\Temporary && $var->original) { + while ($var instanceof Temporary && $var->original) { $var = $var->original; } - if ($var instanceof Operand\Variable) { + if ($var instanceof Variable) { if ($var->name instanceof Literal) { $name = $this->getVariableName($var); - $var = new Operand\Temporary($var); + $var = new Temporary($var); $this->writeVariableName($name, $var, $this->block); } else { $this->readVariable($var->name); // variable variable write - do not resolve the write for now, but we can register the read @@ -1787,7 +420,7 @@ private function writeVariable(Operand $var) return $var; } - private function readVariableName($name, Block $block) + public function readVariableName($name, Block $block): Operand { if ($this->ctx->isLocalVariable($block, $name)) { return $this->ctx->scope[$block][$name]; @@ -1796,19 +429,19 @@ private function readVariableName($name, Block $block) return $this->readVariableRecursive($name, $block); } - private function writeVariableName($name, Operand $value, Block $block) + public function writeVariableName(string $name, Operand $value, Block $block): void { $this->ctx->setValueInScope($block, $name, $value); } - private function readVariableRecursive($name, Block $block) + public function readVariableRecursive(string $name, Block $block): Operand { if ($this->ctx->complete) { if (count($block->parents) === 1 && ! $block->parents[0]->dead) { // Special case, just return the read var return $this->readVariableName($name, $block->parents[0]); } - $var = new Operand\Temporary(new Variable(new Literal($name))); + $var = new Temporary(new Variable(new Literal($name))); $phi = new Op\Phi($var, ['block' => $block]); $block->phi[] = $phi; // Prevent unbound recursion @@ -1823,7 +456,7 @@ private function readVariableRecursive($name, Block $block) return $var; } - $var = new Operand\Temporary(new Variable(new Literal($name))); + $var = new Temporary(new Variable(new Literal($name))); $phi = new Op\Phi($var, ['block' => $block]); $this->ctx->addToIncompletePhis($block, $name, $phi); $this->writeVariableName($name, $var, $block); @@ -1831,7 +464,7 @@ private function readVariableRecursive($name, Block $block) return $var; } - private function getVariableName(Operand\Variable $var) + public function getVariableName(Variable $var): string { assert($var->name instanceof Literal); diff --git a/lib/PHPCfg/ParserHandler.php b/lib/PHPCfg/ParserHandler.php new file mode 100644 index 0000000..24c30e1 --- /dev/null +++ b/lib/PHPCfg/ParserHandler.php @@ -0,0 +1,100 @@ +parser = $parser; + } + + public function getName(): string + { + $name = str_replace([__CLASS__ . '\\', '_'], '', get_class($this)); + return str_replace('\\', '_', $name); + } + + protected function block(?Block $block = null): Block + { + if ($block !== null) { + $this->parser->block = $block; + } + return $this->parser->block; + } + + protected function createBlock(): Block + { + return new Block(); + } + + protected function createBlockWithParent(): Block + { + return new Block($this->parser->block); + } + + protected function createBlockWithCatchTarget(): Block + { + return new Block(null, $this->parser->block->catchTarget); + } + + protected function mapAttributes(Node $node): array + { + return $this->parser->mapAttributes($node); + } + + protected function addOp(Op $op): void + { + $this->parser->block->children[] = $op; + switch ($op->getType()) { + case 'Stmt_JumpIf': + $op->if->addParent($this->parser->block); + $op->else->addParent($this->parser->block); + $this->processAssertions($op->cond, $op->if, $op->else); + break; + case 'Stmt_Jump': + $op->target->addParent($this->parser->block); + break; + } + } + + protected function addExpr(Op\Expr $expr): Operand + { + $this->addOp($expr); + return $expr->result; + } + + protected function processAssertions(Operand $op, Block $if, Block $else): void + { + $block = $this->block(); + foreach ($op->assertions as $assert) { + $this->block($if); + array_unshift($this->block()->children, new Op\Expr\Assertion( + $this->parser->readVariable($assert['var']), + $this->parser->writeVariable($assert['var']), + $this->parser->readAssertion($assert['assertion']), + )); + $this->block($else); + array_unshift($this->block()->children, new Op\Expr\Assertion( + $this->parser->readVariable($assert['var']), + $this->parser->writeVariable($assert['var']), + new Assertion\NegatedAssertion([$this->parser->readAssertion($assert['assertion'])]), + )); + } + $this->block($block); + } + +} diff --git a/lib/PHPCfg/ParserHandler/Batch.php b/lib/PHPCfg/ParserHandler/Batch.php new file mode 100644 index 0000000..9558129 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Batch.php @@ -0,0 +1,16 @@ +mapAttributes($node); + $iterable = $this->parser->readVariable($this->parser->parseExprNode($node->expr)); + $this->addOp(new Op\Iterator\Reset($iterable, $attrs)); + + $loopInit = $this->createBlockWithCatchTarget(); + $loopBody = $this->createBlockWithCatchTarget(); + $loopEnd = $this->createBlockWithCatchTarget(); + + $this->addOp(new Op\Stmt\Jump($loopInit, $attrs)); + + $this->block($loopInit); + $result = $this->addExpr(new Op\Iterator\Valid($iterable, $attrs)); + $this->addOp(new Op\Stmt\JumpIf($result, $loopBody, $loopEnd, $attrs)); + + $this->block($loopBody); + + if ($node->keyVar) { + $this->addOp($keyOp = new Op\Iterator\Key($iterable, $attrs)); + $this->addOp(new Op\Expr\Assign($this->parser->writeVariable($this->parser->parseExprNode($node->keyVar)), $keyOp->result, $attrs)); + } + + $this->addOp($valueOp = new Op\Iterator\Value($iterable, $node->byRef, $attrs)); + + if ($node->valueVar instanceof Node\Expr\List_ || $node->valueVar instanceof Node\Expr\Array_) { + $this->parseListAssignment($node->valueVar, $valueOp->result); + } elseif ($node->byRef) { + $this->addOp(new Op\Expr\AssignRef($this->parser->writeVariable($this->parser->parseExprNode($node->valueVar)), $valueOp->result, $attrs)); + } else { + $this->addOp(new Op\Expr\Assign($this->parser->writeVariable($this->parser->parseExprNode($node->valueVar)), $valueOp->result, $attrs)); + } + + $this->block($this->parser->parseNodes($node->stmts, $this->block())); + $this->addOp(new Op\Stmt\Jump($loopInit, $attrs)); + + $this->block($loopEnd); + } + + public function handleExpr(Node\Expr $expr): Operand + { + $e = $this->parser->readVariable($this->parser->parseExprNode($expr->expr)); + if ($expr->var instanceof Node\Expr\List_ || $expr->var instanceof Node\Expr\Array_) { + $this->parseListAssignment($expr->var, $e); + + return $e; + } + $v = $this->parser->writeVariable($this->parser->parseExprNode($expr->var)); + + return $this->addExpr(new Op\Expr\Assign($v, $e, $this->mapAttributes($expr))); + } + + protected function parseListAssignment(Node\Expr\List_|Node\Expr\Array_ $expr, Operand $rhs): void + { + foreach ($expr->items as $i => $item) { + if (null === $item) { + continue; + } + + if ($item->key === null) { + $key = new Operand\Literal($i); + } else { + $key = $this->parser->readVariable($this->parser->parseExprNode($item->key)); + } + + $var = $item->value; + $result = $this->addExpr(new Op\Expr\ArrayDimFetch($rhs, $key, $this->mapAttributes($expr))); + if ($var instanceof Node\Expr\List_ || $var instanceof Node\Expr\Array_) { + $this->parseListAssignment($var, $result); + + continue; + } + + $this->addOp(new Op\Expr\Assign( + $this->parser->writeVariable($this->parser->parseExprNode($var)), + $result, + $this->mapAttributes($expr), + )); + } + } +} diff --git a/lib/PHPCfg/ParserHandler/Batch/AssignOp.php b/lib/PHPCfg/ParserHandler/Batch/AssignOp.php new file mode 100644 index 0000000..55198ea --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Batch/AssignOp.php @@ -0,0 +1,64 @@ + Op\Expr\BinaryOp\BitwiseAnd::class, + 'Expr_AssignOp_BitwiseOr' => Op\Expr\BinaryOp\BitwiseOr::class, + 'Expr_AssignOp_BitwiseXor' => Op\Expr\BinaryOp\BitwiseXor::class, + 'Expr_AssignOp_Coalesce' => Op\Expr\BinaryOp\Coalesce::class, + 'Expr_AssignOp_Concat' => Op\Expr\BinaryOp\Concat::class, + 'Expr_AssignOp_Div' => Op\Expr\BinaryOp\Div::class, + 'Expr_AssignOp_Minus' => Op\Expr\BinaryOp\Minus::class, + 'Expr_AssignOp_Mod' => Op\Expr\BinaryOp\Mod::class, + 'Expr_AssignOp_Mul' => Op\Expr\BinaryOp\Mul::class, + 'Expr_AssignOp_Plus' => Op\Expr\BinaryOp\Plus::class, + 'Expr_AssignOp_Pow' => Op\Expr\BinaryOp\Pow::class, + 'Expr_AssignOp_ShiftLeft' => Op\Expr\BinaryOp\ShiftLeft::class, + 'Expr_AssignOp_ShiftRight' => Op\Expr\BinaryOp\ShiftRight::class, + ]; + + public function getExprSupport(): array + { + return array_keys(self::MAP); + } + + public function getStmtSupport(): array + { + return []; + } + + public function handleExpr(Node\Expr $expr): Operand + { + $type = $expr->getType(); + if (!isset(self::MAP[$type])) { + throw new RuntimeException("Unknown unary expression type $type"); + } + $class = self::MAP[$type]; + $var = $this->parser->parseExprNode($expr->var); + $read = $this->parser->readVariable($var); + $write = $this->parser->writeVariable($var); + $e = $this->parser->readVariable($this->parser->parseExprNode($expr->expr)); + + $attrs = $this->mapAttributes($expr); + $this->addOp($op = new $class($read, $e, $attrs)); + return $this->addExpr(new Op\Expr\Assign($write, $op->result, $attrs)); + } +} diff --git a/lib/PHPCfg/ParserHandler/Batch/BinaryOp.php b/lib/PHPCfg/ParserHandler/Batch/BinaryOp.php new file mode 100644 index 0000000..646e4d6 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Batch/BinaryOp.php @@ -0,0 +1,125 @@ + '', + 'Expr_BinaryOp_LogicalOr' => '', + 'Expr_BinaryOp_BooleanAnd' => '', + 'Expr_BinaryOp_BooleanOr' => '', + 'Expr_BinaryOp_BitwiseAnd' => Op\Expr\BinaryOp\BitwiseAnd::class, + 'Expr_BinaryOp_BitwiseOr' => Op\Expr\BinaryOp\BitwiseOr::class, + 'Expr_BinaryOp_BitwiseXor' => Op\Expr\BinaryOp\BitwiseXor::class, + 'Expr_BinaryOp_Coalesce' => Op\Expr\BinaryOp\Coalesce::class, + 'Expr_BinaryOp_Concat' => Op\Expr\BinaryOp\Concat::class, + 'Expr_BinaryOp_Div' => Op\Expr\BinaryOp\Div::class, + 'Expr_BinaryOp_Equal' => Op\Expr\BinaryOp\Equal::class, + 'Expr_BinaryOp_Greater' => Op\Expr\BinaryOp\Greater::class, + 'Expr_BinaryOp_GreaterOrEqual' => Op\Expr\BinaryOp\GreaterOrEqual::class, + 'Expr_BinaryOp_Identical' => Op\Expr\BinaryOp\Identical::class, + 'Expr_BinaryOp_LogicalXor' => Op\Expr\BinaryOp\LogicalXor::class, + 'Expr_BinaryOp_Minus' => Op\Expr\BinaryOp\Minus::class, + 'Expr_BinaryOp_Mod' => Op\Expr\BinaryOp\Mod::class, + 'Expr_BinaryOp_Mul' => Op\Expr\BinaryOp\Mul::class, + 'Expr_BinaryOp_NotEqual' => Op\Expr\BinaryOp\NotEqual::class, + 'Expr_BinaryOp_NotIdentical' => Op\Expr\BinaryOp\NotIdentical::class, + 'Expr_BinaryOp_Pipe' => Op\Expr\BinaryOp\Pipe::class, + 'Expr_BinaryOp_Plus' => Op\Expr\BinaryOp\Plus::class, + 'Expr_BinaryOp_Pow' => Op\Expr\BinaryOp\Pow::class, + 'Expr_BinaryOp_ShiftLeft' => Op\Expr\BinaryOp\ShiftLeft::class, + 'Expr_BinaryOp_ShiftRight' => Op\Expr\BinaryOp\ShiftRight::class, + 'Expr_BinaryOp_Smaller' => Op\Expr\BinaryOp\Smaller::class, + 'Expr_BinaryOp_SmallerOrEqual' => Op\Expr\BinaryOp\SmallerOrEqual::class, + 'Expr_BinaryOp_Spaceship' => Op\Expr\BinaryOp\Spaceship::class, + ]; + + public function getExprSupport(): array + { + return array_keys(self::MAP); + } + + public function getStmtSupport(): array + { + return []; + } + + public function handleExpr(Node\Expr $expr): Operand + { + $type = $expr->getType(); + if (!isset(self::MAP[$type])) { + throw new \RuntimeException("Unknown unary expression type $type"); + } + + if ($expr instanceof AstBinaryOp\LogicalAnd || $expr instanceof AstBinaryOp\BooleanAnd) { + return $this->parseShortCircuiting($expr, false); + } + if ($expr instanceof AstBinaryOp\LogicalOr || $expr instanceof AstBinaryOp\BooleanOr) { + return $this->parseShortCircuiting($expr, true); + } + + $class = self::MAP[$type]; + + $left = $this->parser->readVariable($this->parser->parseExprNode($expr->left)); + $right = $this->parser->readVariable($this->parser->parseExprNode($expr->right)); + if (empty($class)) { + throw new RuntimeException('BinaryOp Not Found: ' . $expr->getType()); + } + return $this->addExpr(new $class($left, $right, $this->mapAttributes($expr))); + } + + private function parseShortCircuiting(AstBinaryOp $expr, $isOr): Operand + { + $result = new Operand\Temporary(); + $longBlock = $this->createBlockWithCatchTarget(); + $midBlock = $this->createBlockWithCatchTarget(); + $endBlock = $this->createBlockWithCatchTarget(); + + $left = $this->parser->readVariable($this->parser->parseExprNode($expr->left)); + $if = $isOr ? $midBlock : $longBlock; + $else = $isOr ? $longBlock : $midBlock; + + $this->addOp(new Op\Stmt\JumpIf($left, $if, $else)); + + $this->block($longBlock); + $right = $this->parser->readVariable($this->parser->parseExprNode($expr->right)); + $castResult = $this->addExpr(new Op\Expr\Cast\Bool_($right)); + $this->addOp(new Op\Stmt\Jump($endBlock)); + + $this->block($midBlock); + $this->addOp(new Op\Stmt\Jump($endBlock)); + + $this->block($endBlock); + $phi = new Op\Phi($result, ['block' => $this->block()]); + $phi->addOperand(new Operand\Literal($isOr)); + $phi->addOperand($castResult); + $this->block()->phi[] = $phi; + + $mode = $isOr ? Assertion::MODE_UNION : Assertion::MODE_INTERSECTION; + foreach ($left->assertions as $assert) { + $result->addAssertion($assert['var'], $assert['assertion'], $mode); + } + foreach ($right->assertions as $assert) { + $result->addAssertion($assert['var'], $assert['assertion'], $mode); + } + + return $result; + } +} diff --git a/lib/PHPCfg/ParserHandler/Batch/IncDec.php b/lib/PHPCfg/ParserHandler/Batch/IncDec.php new file mode 100644 index 0000000..6421830 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Batch/IncDec.php @@ -0,0 +1,62 @@ + Op\Expr\BinaryOp\Minus::class, + 'Expr_PostInc' => Op\Expr\BinaryOp\Plus::class, + 'Expr_PreDec' => Op\Expr\BinaryOp\Minus::class, + 'Expr_PreInc' => Op\Expr\BinaryOp\Plus::class, + ]; + + public function getExprSupport(): array + { + return array_keys(self::MAP); + } + + public function getStmtSupport(): array + { + return []; + } + + public function handleExpr(Node\Expr $expr): Operand + { + $type = $expr->getType(); + if (!isset(self::MAP[$type])) { + throw new RuntimeException("Unknown unary expression type $type"); + } + + $class = self::MAP[$type]; + + $var = $this->parser->parseExprNode($expr->var); + $read = $this->parser->readVariable($var); + $write = $this->parser->writeVariable($var); + + $this->addOp($op = new $class($read, new Operand\Literal(1), $this->mapAttributes($expr))); + $this->addOp(new Op\Expr\Assign($write, $op->result, $this->mapAttributes($expr))); + + if (strpos($type, 'Pre') !== false) { + return $op->result; + } else { + return $read; + } + } + +} diff --git a/lib/PHPCfg/ParserHandler/Batch/Nop.php b/lib/PHPCfg/ParserHandler/Batch/Nop.php new file mode 100644 index 0000000..4c3059d --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Batch/Nop.php @@ -0,0 +1,37 @@ + true, + 'Stmt_Nop' => true, + 'Stmt_Use' => true, + ]; + + public function getExprSupport(): array + { + return []; + } + + public function getStmtSupport(): array + { + return array_keys(self::MAP); + } + + public function handleStmt(Node\Stmt $node): void {} +} diff --git a/lib/PHPCfg/ParserHandler/Batch/Scalar.php b/lib/PHPCfg/ParserHandler/Batch/Scalar.php new file mode 100644 index 0000000..7523ff5 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Batch/Scalar.php @@ -0,0 +1,85 @@ + true, + 'Scalar_Float' => true, + 'Scalar_Int' => true, + 'Scalar_InterpolatedString' => true, + 'Scalar_LNumber' => true, + 'Scalar_String' => true, + 'Scalar_MagicConst_Class' => true, + 'Scalar_MagicConst_Dir' => true, + 'Scalar_MagicConst_File' => true, + 'Scalar_MagicConst_Namespace' => true, + 'Scalar_MagicConst_Method' => true, + 'Scalar_MagicConst_Function' => true, + ]; + + public function getExprSupport(): array + { + return array_keys(self::MAP); + } + + public function getStmtSupport(): array + { + return []; + } + + public function handleExpr(Node\Expr $scalar): Operand + { + switch ($scalar->getType()) { + case 'Scalar_InterpolatedString': + case 'Scalar_Encapsed': + return $this->addExpr(new Op\Expr\ConcatList( + $this->parser->parseExprList($scalar->parts, Parser::MODE_READ), + $this->mapAttributes($scalar) + )); + case 'Scalar_Float': + case 'Scalar_Int': + case 'Scalar_LNumber': + case 'Scalar_String': + case 'Scalar_InterpolatedStringPart': + case 'Scalar_EncapsedStringPart': + return new Operand\Literal($scalar->value); + case 'Scalar_MagicConst_Class': + // TODO + return new Operand\Literal('__CLASS__'); + case 'Scalar_MagicConst_Dir': + return new Operand\Literal(dirname($this->parser->fileName)); + case 'Scalar_MagicConst_File': + return new Operand\Literal($this->parser->fileName); + case 'Scalar_MagicConst_Namespace': + // TODO + return new Operand\Literal('__NAMESPACE__'); + case 'Scalar_MagicConst_Method': + // TODO + return new Operand\Literal('__METHOD__'); + case 'Scalar_MagicConst_Function': + // TODO + return new Operand\Literal('__FUNCTION__'); + default: + var_dump($scalar); + throw new RuntimeException('Unknown how to deal with scalar type ' . $scalar->getType()); + } + } +} diff --git a/lib/PHPCfg/ParserHandler/Batch/Unary.php b/lib/PHPCfg/ParserHandler/Batch/Unary.php new file mode 100644 index 0000000..921c09e --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Batch/Unary.php @@ -0,0 +1,72 @@ + Op\Expr\BitwiseNot::class, + 'Expr_BooleanNot' => Op\Expr\BooleanNot::class, + 'Expr_Cast_Array' => Op\Expr\Cast\Array_::class, + 'Expr_Cast_Bool' => Op\Expr\Cast\Bool_::class, + 'Expr_Cast_Double' => Op\Expr\Cast\Double::class, + 'Expr_Cast_Int' => Op\Expr\Cast\Int_::class, + 'Expr_Cast_Object' => Op\Expr\Cast\Object_::class, + 'Expr_Cast_String' => Op\Expr\Cast\String_::class, + 'Expr_Cast_Unset' => Op\Expr\Cast\Unset_::class, + 'Expr_Cast_Void' => Op\Expr\Cast\Void_::class, + 'Expr_Empty' => Op\Expr\Empty_::class, + 'Expr_Eval' => Op\Expr\Eval_::class, + 'Expr_Print' => Op\Expr\Print_::class, + 'Expr_UnaryMinus' => Op\Expr\UnaryMinus::class, + 'Expr_UnaryPlus' => Op\Expr\UnaryPlus::class, + ]; + + + public function getExprSupport(): array + { + return array_keys(self::MAP); + } + + public function getStmtSupport(): array + { + return []; + } + + public function handleExpr(Node\Expr $expr): Operand + { + $type = $expr->getType(); + if (!isset(self::MAP[$type])) { + throw new RuntimeException("Unknown unary expression type $type"); + } + $class = self::MAP[$type]; + $result = $this->addExpr(new $class( + $cond = $this->parser->readVariable($this->parser->parseExprNode($expr->expr)), + $this->mapAttributes($expr) + )); + + if ($expr instanceof Node\Expr\BooleanNot) { + // process type assertions + foreach ($cond->assertions as $assertion) { + $result->addAssertion($assertion['var'], new Assertion\NegatedAssertion([$assertion['assertion']])); + } + } + + return $result; + } +} diff --git a/lib/PHPCfg/ParserHandler/Expr.php b/lib/PHPCfg/ParserHandler/Expr.php new file mode 100644 index 0000000..e8d1f6d --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr.php @@ -0,0 +1,19 @@ +parser->readVariable($this->parser->parseExprNode($expr->var)); + if (null !== $expr->dim) { + $d = $this->parser->readVariable($this->parser->parseExprNode($expr->dim)); + } else { + $d = new Operand\NullOperand(); + } + + return $this->addExpr(new Op\Expr\ArrayDimFetch($v, $d, $this->mapAttributes($expr))); + } + +} diff --git a/lib/PHPCfg/ParserHandler/Expr/Array_.php b/lib/PHPCfg/ParserHandler/Expr/Array_.php new file mode 100644 index 0000000..d313d64 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/Array_.php @@ -0,0 +1,40 @@ +items) { + foreach ($expr->items as $item) { + if ($item->key) { + $keys[] = $this->parser->readVariable($this->parser->parseExprNode($item->key)); + } else { + $keys[] = new Operand\NullOperand(); + } + $values[] = $this->parser->readVariable($this->parser->parseExprNode($item->value)); + $byRef[] = $item->byRef; + } + } + + return $this->addExpr($array = new Op\Expr\Array_($keys, $values, $byRef, $this->mapAttributes($expr))); + } + +} diff --git a/lib/PHPCfg/ParserHandler/Expr/ArrowFunction.php b/lib/PHPCfg/ParserHandler/Expr/ArrowFunction.php new file mode 100644 index 0000000..e65a296 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/ArrowFunction.php @@ -0,0 +1,43 @@ +byRef ? Func::FLAG_RETURNS_REF : 0; + $flags |= $expr->static ? Func::FLAG_STATIC : 0; + + $this->parser->script->functions[] = $func = new Func( + '{anonymous}#' . ++$this->parser->anonId, + $flags, + $this->parser->parseTypeNode($expr->returnType), + null, + ); + $stmts = [ + new Node\Stmt\Return_($expr->expr), + ]; + $this->parser->parseFunc($func, $expr->params, $stmts); + + $closure = new Op\Expr\ArrowFunction($func, $this->mapAttributes($expr)); + $func->callableOp = $closure; + + return $this->addExpr($closure); + } +} diff --git a/lib/PHPCfg/ParserHandler/Expr/AssignRef.php b/lib/PHPCfg/ParserHandler/Expr/AssignRef.php new file mode 100644 index 0000000..11b1e7d --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/AssignRef.php @@ -0,0 +1,28 @@ +parser->readVariable($this->parser->parseExprNode($expr->expr)); + $v = $this->parser->writeVariable($this->parser->parseExprNode($expr->var)); + + return $this->addExpr(new Op\Expr\AssignRef($v, $e, $this->mapAttributes($expr))); + } + +} diff --git a/lib/PHPCfg/ParserHandler/Expr/ClassConstFetch.php b/lib/PHPCfg/ParserHandler/Expr/ClassConstFetch.php new file mode 100644 index 0000000..1ad7342 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/ClassConstFetch.php @@ -0,0 +1,29 @@ +addExpr(new Op\Expr\ClassConstFetch( + $this->parser->readVariable($this->parser->parseExprNode($expr->class)), + $this->parser->readVariable($this->parser->parseExprNode($expr->name)), + $this->mapAttributes($expr) + )); + } +} diff --git a/lib/PHPCfg/ParserHandler/Expr/Clone_.php b/lib/PHPCfg/ParserHandler/Expr/Clone_.php new file mode 100644 index 0000000..409522e --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/Clone_.php @@ -0,0 +1,29 @@ +addExpr(new Op\Expr\Clone_( + $this->parser->readVariable($this->parser->parseExprNode($expr->expr)), + $this->mapAttributes($expr) + )); + + } + +} diff --git a/lib/PHPCfg/ParserHandler/Expr/Closure_.php b/lib/PHPCfg/ParserHandler/Expr/Closure_.php new file mode 100644 index 0000000..9d446c5 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/Closure_.php @@ -0,0 +1,49 @@ +uses as $use) { + $uses[] = new Operand\BoundVariable( + $this->parser->readVariable(new Operand\Literal($use->var->name)), + $use->byRef, + Operand\BoundVariable::SCOPE_LOCAL, + ); + } + + $flags = Func::FLAG_CLOSURE; + $flags |= $expr->byRef ? Func::FLAG_RETURNS_REF : 0; + $flags |= $expr->static ? Func::FLAG_STATIC : 0; + + $this->parser->script->functions[] = $func = new Func( + '{anonymous}#' . ++$this->parser->anonId, + $flags, + $this->parser->parseTypeNode($expr->returnType), + null, + ); + $this->parser->parseFunc($func, $expr->params, $expr->stmts, null); + + $closure = new Op\Expr\Closure($func, $uses, $this->mapAttributes($expr)); + $func->callableOp = $closure; + + return $this->addExpr($closure); + } +} diff --git a/lib/PHPCfg/ParserHandler/Expr/ConstFetch.php b/lib/PHPCfg/ParserHandler/Expr/ConstFetch.php new file mode 100644 index 0000000..395f2a0 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/ConstFetch.php @@ -0,0 +1,45 @@ +name->isUnqualified()) { + $lcname = strtolower($expr->name->toString()); + switch ($lcname) { + case 'null': + return new Operand\Literal(null); + case 'true': + return new Operand\Literal(true); + case 'false': + return new Operand\Literal(false); + } + } + + $nsName = null; + if ($this->parser->currentNamespace && $expr->name->isUnqualified()) { + $nsName = $this->parser->parseExprNode(Node\Name::concat($this->parser->currentNamespace, $expr->name)); + } + + return $this->addExpr(new Op\Expr\ConstFetch( + $this->parser->parseExprNode($expr->name), + $nsName, + $this->mapAttributes($expr) + )); + } +} diff --git a/lib/PHPCfg/ParserHandler/Expr/ErrorSuppress.php b/lib/PHPCfg/ParserHandler/Expr/ErrorSuppress.php new file mode 100644 index 0000000..aa3c768 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/ErrorSuppress.php @@ -0,0 +1,36 @@ +mapAttributes($expr); + $block = new ErrorSuppressBlock(); + $this->addOp(new Op\Stmt\Jump($block, $attrs)); + $this->block($block); + + $result = $this->parser->parseExprNode($expr->expr); + + $end = $this->createBlockWithCatchTarget(); + $this->addOp(new Op\Stmt\Jump($end, $attrs)); + $this->block($end); + + return $result; + } +} diff --git a/lib/PHPCfg/ParserHandler/Expr/Exit_.php b/lib/PHPCfg/ParserHandler/Expr/Exit_.php new file mode 100644 index 0000000..216b7fd --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/Exit_.php @@ -0,0 +1,34 @@ +expr) { + $e = $this->parser->readVariable($this->parser->parseExprNode($expr->expr)); + } + + $this->addOp(new Op\Terminal\Exit_($e, $this->mapAttributes($expr))); + // Dump everything after the exit + $this->block($this->createBlockWithCatchTarget()); + $this->block()->dead = true; + + return new Operand\Literal(1); + } +} diff --git a/lib/PHPCfg/ParserHandler/Expr/FuncCall.php b/lib/PHPCfg/ParserHandler/Expr/FuncCall.php new file mode 100644 index 0000000..362e739 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/FuncCall.php @@ -0,0 +1,65 @@ +parser->parseExprList($expr->args, Parser::MODE_READ); + $name = $this->parser->readVariable($this->parser->parseExprNode($expr->name)); + if ($this->parser->currentNamespace && $expr->name instanceof Node\Name && $expr->name->isUnqualified()) { + $op = new Op\Expr\NsFuncCall( + $name, + $this->parser->parseExprNode(Node\Name::concat($this->parser->currentNamespace, $expr->name)), + $args, + $this->mapAttributes($expr), + ); + } else { + $op = new Op\Expr\FuncCall($name, $args, $this->mapAttributes($expr)); + } + + if ($name instanceof Operand\Literal) { + static $assertionFunctions = [ + 'is_array' => 'array', + 'is_bool' => 'bool', + 'is_callable' => 'callable', + 'is_double' => 'float', + 'is_float' => 'float', + 'is_int' => 'int', + 'is_integer' => 'int', + 'is_long' => 'int', + 'is_null' => 'null', + 'is_numeric' => 'numeric', + 'is_object' => 'object', + 'is_real' => 'float', + 'is_string' => 'string', + 'is_resource' => 'resource', + ]; + $lname = strtolower($name->value); + if (isset($assertionFunctions[$lname])) { + $op->result->addAssertion( + $args[0], + new Assertion\TypeAssertion(new Operand\Literal($assertionFunctions[$lname])), + ); + } + } + + return $this->addExpr($op); + } +} diff --git a/lib/PHPCfg/ParserHandler/Expr/Include_.php b/lib/PHPCfg/ParserHandler/Expr/Include_.php new file mode 100644 index 0000000..70a2db4 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/Include_.php @@ -0,0 +1,28 @@ +addExpr(new Op\Expr\Include_( + $this->parser->readVariable($this->parser->parseExprNode($expr->expr)), + $expr->type, + $this->mapAttributes($expr) + )); + } +} diff --git a/lib/PHPCfg/ParserHandler/Expr/Instanceof_.php b/lib/PHPCfg/ParserHandler/Expr/Instanceof_.php new file mode 100644 index 0000000..0775c02 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/Instanceof_.php @@ -0,0 +1,34 @@ +parser->readVariable($this->parser->parseExprNode($expr->expr)); + $class = $this->parser->readVariable($this->parser->parseExprNode($expr->class)); + $result = $this->addExpr(new Op\Expr\InstanceOf_( + $var, + $class, + $this->mapAttributes($expr), + )); + $result->addAssertion($var, new Assertion\TypeAssertion($class)); + + return $result; + } +} diff --git a/lib/PHPCfg/ParserHandler/Expr/Isset_.php b/lib/PHPCfg/ParserHandler/Expr/Isset_.php new file mode 100644 index 0000000..9cdb871 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/Isset_.php @@ -0,0 +1,28 @@ +addExpr(new Op\Expr\Isset_( + $this->parser->parseExprList($expr->vars, Parser::MODE_READ), + $this->mapAttributes($expr), + )); + } +} diff --git a/lib/PHPCfg/ParserHandler/Expr/Match_.php b/lib/PHPCfg/ParserHandler/Expr/Match_.php new file mode 100644 index 0000000..3ff6e54 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/Match_.php @@ -0,0 +1,122 @@ +canImplementJumpTable($expr)) { + return $this->implementJumpTable($expr); + } + + return $this->implementNonJumpTable($expr); + } + + + private function canImplementJumpTable(Node\Expr\Match_ $expr): bool + { + foreach ($expr->arms as $arm) { + foreach ($arm->conds as $cond) { + if ( + null !== $cond + && ! $cond instanceof Node\Scalar + ) { + return false; + } + } + } + return true; + } + + private function implementNonJumpTable(Node\Expr\Match_ $expr): Operand + { + $endResult = new Operand\Temporary(); + $endBlock = $this->createBlockWithCatchTarget(); + $phi = new Op\Phi($endResult, ['block' => $endBlock]); + $endBlock->phi[] = $phi; + $matchCond = $this->parser->readVariable($this->parser->parseExprNode($expr->cond)); + foreach ($expr->arms as $arm) { + $current = $this->block(); + $block = $this->block($this->createBlockWithParent()); + $result = $this->ensureTemporary($this->parser->parseExprNode($arm->body)); + $phi->addOperand($result); + if (empty($block->children)) { + $block = $endBlock; + } else { + $this->addOp(new Op\Stmt\Jump($endBlock), $this->mapAttributes($arm)); + } + $this->block($current); + foreach ($arm->conds as $cond) { + $var = $this->parser->readVariable($this->parser->parseExprNode($cond)); + $test = $this->addExpr(new Op\Expr\BinaryOp\Identical($var, $matchCond, $this->mapAttributes($cond))); + $next = $this->createBlockWithCatchTarget(); + $this->addOp(new Op\Stmt\JumpIf($test, $block, $next, $this->mapAttributes($cond))); + $this->block($next); + } + } + //Compile Error Block + $this->addOp(new Op\Terminal\MatchError($matchCond)); + $this->block($endBlock); + return $endResult; + } + + private function implementJumpTable(Node\Expr\Match_ $expr): Operand + { + $endBlock = $this->createBlockWithCatchTarget(); + $matchCond = $this->parser->readVariable($this->parser->parseExprNode($expr->cond)); + $thisBlock = $this->block(); + $table = []; + foreach ($expr->arms as $arm) { + $block = $this->block($this->createBlockWithParent()); + $result = $this->ensureTemporary($this->parser->parseExprNode($arm->body)); + if (empty($block->children)) { + $block = $endBlock; + } else { + $this->addOp(new Op\Stmt\Jump($endBlock, $this->mapAttributes($arm))); + } + $this->block($thisBlock); + foreach ($arm->conds as $cond) { + $table[] = [$this->parser->readVariable($this->parser->parseExprNode($cond)), $block, $result]; + } + } + $this->addOp($match = new Op\Stmt\MatchTable( + $matchCond, + $this->mapAttributes($expr) + )); + $endResult = new Operand\Temporary(); + $phi = new Op\Phi($endResult, ['block' => $endBlock]); + $endBlock->phi[] = $phi; + foreach ($table as [$case, $block, $result]) { + $phi->addOperand($result); + $match->addArm($case, $block); + } + + $this->block($endBlock); + + return $endResult; + } + + private function ensureTemporary(Operand $result): Operand + { + if ($result instanceof Operand\Literal) { + $this->addOp(new Op\Expr\Assign($r = new Operand\Temporary(), $result)); + return $r; + } + return $result; + } + +} diff --git a/lib/PHPCfg/ParserHandler/Expr/MethodCall.php b/lib/PHPCfg/ParserHandler/Expr/MethodCall.php new file mode 100644 index 0000000..b8848ee --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/MethodCall.php @@ -0,0 +1,30 @@ +addExpr(new Op\Expr\MethodCall( + $this->parser->readVariable($this->parser->parseExprNode($expr->var)), + $this->parser->readVariable($this->parser->parseExprNode($expr->name)), + $this->parser->parseExprList($expr->args, Parser::MODE_READ), + $this->mapAttributes($expr), + )); + } +} diff --git a/lib/PHPCfg/ParserHandler/Expr/New_.php b/lib/PHPCfg/ParserHandler/Expr/New_.php new file mode 100644 index 0000000..3af05d1 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/New_.php @@ -0,0 +1,36 @@ +class instanceof Node\Stmt\Class_) { + $this->parser->parseNode($expr->class); + $classExpr = $expr->class->name; + } else { + $classExpr = $expr->class; + } + + return $this->addExpr(new Op\Expr\New_( + $this->parser->readVariable($this->parser->parseExprNode($classExpr)), + $this->parser->parseExprList($expr->args, Parser::MODE_READ), + $this->mapAttributes($expr), + )); + } +} diff --git a/lib/PHPCfg/ParserHandler/Expr/PropertyFetch.php b/lib/PHPCfg/ParserHandler/Expr/PropertyFetch.php new file mode 100644 index 0000000..c783972 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/PropertyFetch.php @@ -0,0 +1,28 @@ +addExpr(new Op\Expr\PropertyFetch( + $this->parser->readVariable($this->parser->parseExprNode($expr->var)), + $this->parser->readVariable($this->parser->parseExprNode($expr->name)), + $this->mapAttributes($expr), + )); + } +} diff --git a/lib/PHPCfg/ParserHandler/Expr/ShellExec.php b/lib/PHPCfg/ParserHandler/Expr/ShellExec.php new file mode 100644 index 0000000..786b826 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/ShellExec.php @@ -0,0 +1,34 @@ +addExpr(new Op\Expr\ConcatList( + $this->parser->parseExprList($expr->parts, Parser::MODE_READ), + $this->mapAttributes($expr), + )); + + return $this->addExpr(new Op\Expr\FuncCall( + new Operand\Literal('shell_exec'), + [$result], + $this->mapAttributes($expr), + )); + } +} diff --git a/lib/PHPCfg/ParserHandler/Expr/StaticCall.php b/lib/PHPCfg/ParserHandler/Expr/StaticCall.php new file mode 100644 index 0000000..09009ae --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/StaticCall.php @@ -0,0 +1,30 @@ +addExpr(new Op\Expr\StaticCall( + $this->parser->readVariable($this->parser->parseExprNode($expr->class)), + $this->parser->readVariable($this->parser->parseExprNode($expr->name)), + $this->parser->parseExprList($expr->args, Parser::MODE_READ), + $this->mapAttributes($expr), + )); + } +} diff --git a/lib/PHPCfg/ParserHandler/Expr/StaticPropertyFetch.php b/lib/PHPCfg/ParserHandler/Expr/StaticPropertyFetch.php new file mode 100644 index 0000000..4ee0615 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/StaticPropertyFetch.php @@ -0,0 +1,28 @@ +addExpr(new Op\Expr\StaticPropertyFetch( + $this->parser->readVariable($this->parser->parseExprNode($expr->class)), + $this->parser->readVariable($this->parser->parseExprNode($expr->name)), + $this->mapAttributes($expr), + )); + } +} diff --git a/lib/PHPCfg/ParserHandler/Expr/Ternary.php b/lib/PHPCfg/ParserHandler/Expr/Ternary.php new file mode 100644 index 0000000..6bdf0ee --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/Ternary.php @@ -0,0 +1,63 @@ +mapAttributes($expr); + $cond = $this->parser->readVariable($this->parser->parseExprNode($expr->cond)); + + $ifBlock = $this->createBlockWithCatchTarget(); + $elseBlock = $this->createBlockWithCatchTarget(); + $endBlock = $this->createBlockWithCatchTarget(); + + $this->addOp(new Op\Stmt\JumpIf($cond, $ifBlock, $elseBlock, $attrs)); + + $this->block($ifBlock); + $ifVar = new Operand\Temporary(); + + if ($expr->if) { + $this->addOp(new Op\Expr\Assign( + $ifVar, + $this->parser->readVariable($this->parser->parseExprNode($expr->if)), + $attrs, + )); + } else { + $this->addOp(new Op\Expr\Assign($ifVar, $cond, $attrs)); + } + $this->addOp(new Op\Stmt\Jump($endBlock, $attrs)); + + $this->block($elseBlock); + $elseVar = new Operand\Temporary(); + $this->addOp(new Op\Expr\Assign( + $elseVar, + $this->parser->readVariable($this->parser->parseExprNode($expr->else)), + $attrs, + )); + $this->addOp(new Op\Stmt\Jump($endBlock, $attrs)); + + $this->block($endBlock); + $result = new Operand\Temporary(); + $phi = new Op\Phi($result, ['block' => $this->block()]); + $phi->addOperand($ifVar); + $phi->addOperand($elseVar); + $this->block()->phi[] = $phi; + + return $result; + } +} diff --git a/lib/PHPCfg/ParserHandler/Expr/Throw_.php b/lib/PHPCfg/ParserHandler/Expr/Throw_.php new file mode 100644 index 0000000..3872ab2 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/Throw_.php @@ -0,0 +1,31 @@ +addOp(new Op\Terminal\Throw_( + $this->parser->readVariable($this->parser->parseExprNode($expr->expr)), + $this->mapAttributes($expr) + )); + $this->block($this->createBlockWithCatchTarget()); + $this->block()->dead = true; + + return new Operand\Literal(1); + } +} diff --git a/lib/PHPCfg/ParserHandler/Expr/Variable.php b/lib/PHPCfg/ParserHandler/Expr/Variable.php new file mode 100644 index 0000000..0417713 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/Variable.php @@ -0,0 +1,41 @@ +name)) { + if ($expr->name === 'this') { + return new Operand\BoundVariable( + $this->parser->parseExprNode($expr->name), + false, + Operand\BoundVariable::SCOPE_OBJECT, + $this->parser->currentClass, + ); + } + + return new Operand\Variable($this->parser->parseExprNode($expr->name)); + } + + // variable variable + return $this->addExpr(new Op\Expr\VarVar( + $this->parser->readVariable($this->parser->parseExprNode($expr->name)), + $this->mapAttributes($expr) + )); + } +} diff --git a/lib/PHPCfg/ParserHandler/Expr/YieldFrom.php b/lib/PHPCfg/ParserHandler/Expr/YieldFrom.php new file mode 100644 index 0000000..f7ce381 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/YieldFrom.php @@ -0,0 +1,27 @@ +parser->readVariable($this->parser->parseExprNode($expr->expr)); + + return $this->addExpr(new Op\Expr\YieldFrom($from, $this->mapAttributes($expr))); + } + +} diff --git a/lib/PHPCfg/ParserHandler/Expr/Yield_.php b/lib/PHPCfg/ParserHandler/Expr/Yield_.php new file mode 100644 index 0000000..49f4688 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/Yield_.php @@ -0,0 +1,34 @@ +key) { + $key = $this->parser->readVariable($this->parser->parseExprNode($expr->key)); + } + if ($expr->value) { + $key = $this->parser->readVariable($this->parser->parseExprNode($expr->value)); + } + + return $this->addExpr(new Op\Expr\Yield_($value, $key, $this->mapAttributes($expr))); + } + +} diff --git a/lib/PHPCfg/ParserHandler/Stmt.php b/lib/PHPCfg/ParserHandler/Stmt.php new file mode 100644 index 0000000..bd84e8b --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt.php @@ -0,0 +1,18 @@ +parser->parseNodes($node->stmts, $this->block()); + } +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/ClassConst.php b/lib/PHPCfg/ParserHandler/Stmt/ClassConst.php new file mode 100644 index 0000000..0b337d5 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/ClassConst.php @@ -0,0 +1,39 @@ +parser->currentClass instanceof Op\Type\Literal) { + throw new RuntimeException('Unknown current class'); + } + foreach ($node->consts as $const) { + $tmp = $this->block(); + $valueBlock = $this->block($this->createBlock()); + $value = $this->parser->parseExprNode($const->value); + $this->block($tmp); + + $this->addOp(new Op\Terminal\Const_( + $this->parser->parseExprNode($const->name), + $value, + $valueBlock, + $this->mapAttributes($node), + )); + } + } +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/ClassMethod.php b/lib/PHPCfg/ParserHandler/Stmt/ClassMethod.php new file mode 100644 index 0000000..0747ee8 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/ClassMethod.php @@ -0,0 +1,58 @@ +parser->currentClass instanceof Op\Type\Literal) { + throw new RuntimeException('Unknown current class'); + } + + $this->parser->script->functions[] = $func = new Func( + $node->name->toString(), + $node->flags | ($node->byRef ? Func::FLAG_RETURNS_REF : 0), + $this->parser->parseTypeNode($node->returnType), + $this->parser->currentClass, + ); + + if ($node->stmts !== null) { + $this->parser->parseFunc($func, $node->params, $node->stmts, null); + } else { + $func->params = $this->parser->parseParameterList($func, $node->params); + $func->cfg = null; + } + + $visibility = $node->flags & Modifiers::VISIBILITY_MASK; + $static = $node->flags & Modifiers::STATIC; + $final = $node->flags & Modifiers::FINAL; + $abstract = $node->flags & Modifiers::ABSTRACT; + + $this->addOp($class_method = new Op\Stmt\ClassMethod( + $func, + $visibility, + (bool) $static, + (bool) $final, + (bool) $abstract, + $this->parser->parseAttributeGroups(...$node->attrGroups), + $this->mapAttributes($node), + )); + $func->callableOp = $class_method; + } +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/Class_.php b/lib/PHPCfg/ParserHandler/Stmt/Class_.php new file mode 100644 index 0000000..6028a9d --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Class_.php @@ -0,0 +1,36 @@ +parser->parseTypeNode($node->namespacedName); + $old = $this->parser->currentClass; + $this->parser->currentClass = $name; + + $this->addOp(new Op\Stmt\Class_( + $name, + $node->flags, + $node->extends ? $this->parser->parseTypeNode($node->extends) : null, + $this->parser->parseTypeList(...$node->implements), + $this->parser->parseNodes($node->stmts, $this->createBlock()), + $this->parser->parseAttributeGroups(...$node->attrGroups), + $this->mapAttributes($node), + )); + $this->parser->currentClass = $old; + } +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/Const_.php b/lib/PHPCfg/ParserHandler/Stmt/Const_.php new file mode 100644 index 0000000..31e18ea --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Const_.php @@ -0,0 +1,35 @@ +consts as $const) { + $tmp = $this->block(); + $valueBlock = $this->block($this->createBlock()); + $value = $this->parser->parseExprNode($const->value); + $this->block($tmp); + + $this->addOp(new Op\Terminal\Const_( + $this->parser->parseExprNode($const->namespacedName), + $value, + $valueBlock, + $this->mapAttributes($node), + )); + } + } +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/Declare_.php b/lib/PHPCfg/ParserHandler/Stmt/Declare_.php new file mode 100644 index 0000000..69e4647 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Declare_.php @@ -0,0 +1,37 @@ +declares as $declare) { + switch ($declare->key->toLowerString()) { + case 'ticks': + $this->parser->script->ticks = $declare->value->value; + break; + case 'strict_types': + $this->parser->script->strict_types = (bool) $declare->value->value; + break; + case 'encoding': + $this->parser->script->encoding = $declare->value->value; + break; + default: + throw new LogicException("Unknown declare key found: " . $declare->key); + } + } + } +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/Do_.php b/lib/PHPCfg/ParserHandler/Stmt/Do_.php new file mode 100644 index 0000000..0de0469 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Do_.php @@ -0,0 +1,33 @@ +createBlockWithCatchTarget(); + $loopEnd = $this->createBlockWithCatchTarget(); + $this->addOp(new Op\Stmt\Jump($loopBody, $this->mapAttributes($node))); + + $this->block($loopBody); + $this->block($this->parser->parseNodes($node->stmts, $loopBody)); + $cond = $this->parser->readVariable($this->parser->parseExprNode($node->cond)); + $this->addOp(new Op\Stmt\JumpIf($cond, $loopBody, $loopEnd, $this->mapAttributes($node))); + $this->processAssertions($cond, $loopBody, $loopEnd); + + $this->block($loopEnd); + } +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/Echo_.php b/lib/PHPCfg/ParserHandler/Stmt/Echo_.php new file mode 100644 index 0000000..d0d319a --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Echo_.php @@ -0,0 +1,28 @@ +exprs as $expr) { + $this->addOp(new Op\Terminal\Echo_( + $this->parser->readVariable($this->parser->parseExprNode($expr)), + $this->mapAttributes($expr), + )); + } + } +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/Expression.php b/lib/PHPCfg/ParserHandler/Stmt/Expression.php new file mode 100644 index 0000000..b4a7d44 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Expression.php @@ -0,0 +1,22 @@ +parser->parseExprNode($node->expr); + } +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/For_.php b/lib/PHPCfg/ParserHandler/Stmt/For_.php new file mode 100644 index 0000000..b9b5629 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/For_.php @@ -0,0 +1,43 @@ +parser->parseExprList($node->init, Parser::MODE_READ); + + $loopInit = $this->createBlockWithCatchTarget(); + $loopBody = $this->createBlockWithCatchTarget(); + $loopEnd = $this->createBlockWithCatchTarget(); + + $this->addOp(new Op\Stmt\Jump($loopInit, $this->mapAttributes($node))); + $this->block($loopInit); + if (! empty($node->cond)) { + $cond = $this->parser->readVariable($this->parser->parseExprNode($node->cond)); + } else { + $cond = new Operand\Literal(true); + } + $this->addOp(new Op\Stmt\JumpIf($cond, $loopBody, $loopEnd, $this->mapAttributes($node))); + + $this->block($this->parser->parseNodes($node->stmts, $loopBody)); + $this->parser->parseExprList($node->loop, Parser::MODE_READ); + $this->addOp(new Op\Stmt\Jump($loopInit, $this->mapAttributes($node))); + $this->block($loopEnd); + } +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/Function_.php b/lib/PHPCfg/ParserHandler/Stmt/Function_.php new file mode 100644 index 0000000..c8979eb --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Function_.php @@ -0,0 +1,36 @@ +parser->script->functions[] = $func = new Func( + $node->namespacedName->toString(), + $node->byRef ? Func::FLAG_RETURNS_REF : 0, + $this->parser->parseTypeNode($node->returnType), + null, + ); + $this->parser->parseFunc($func, $node->params, $node->stmts, null); + $this->addOp($function = new Op\Stmt\Function_( + $func, + $this->parser->parseAttributeGroups(...$node->attrGroups), + $this->mapAttributes($node) + )); + $func->callableOp = $function; + } +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/Global_.php b/lib/PHPCfg/ParserHandler/Stmt/Global_.php new file mode 100644 index 0000000..36f1fe1 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Global_.php @@ -0,0 +1,29 @@ +vars as $var) { + // TODO $var is not necessarily a Variable node + $this->addOp(new Op\Terminal\GlobalVar( + $this->parser->writeVariable($this->parser->parseExprNode($var->name)), + $this->mapAttributes($node), + )); + } + } +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/Goto_.php b/lib/PHPCfg/ParserHandler/Stmt/Goto_.php new file mode 100644 index 0000000..866b66d --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Goto_.php @@ -0,0 +1,30 @@ +mapAttributes($node); + if (isset($this->parser->ctx->labels[$node->name->toString()])) { + $labelBlock = $this->parser->ctx->labels[$node->name->toString()]; + $this->addOp(new Op\Stmt\Jump($labelBlock, $attributes)); + } else { + $this->parser->ctx->unresolvedGotos[$node->name->toString()][] = [$this->block(), $attributes]; + } + $this->block($this->createBlock())->dead = true; + } +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/HaltCompiler.php b/lib/PHPCfg/ParserHandler/Stmt/HaltCompiler.php new file mode 100644 index 0000000..0cbb53b --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/HaltCompiler.php @@ -0,0 +1,27 @@ +addOp(new Op\Terminal\Echo_( + $this->parser->readVariable(new Operand\Literal($node->remaining)), + $this->mapAttributes($node), + )); + } +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/If_.php b/lib/PHPCfg/ParserHandler/Stmt/If_.php new file mode 100644 index 0000000..f9ff6d8 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/If_.php @@ -0,0 +1,52 @@ +createBlockWithCatchTarget(); + $this->parseIf($node, $endBlock); + $this->block($endBlock); + } + + protected function parseIf(Node\Stmt $node, Block $endBlock): void + { + $attrs = $this->mapAttributes($node); + $cond = $this->parser->readVariable($this->parser->parseExprNode($node->cond)); + $ifBlock = $this->createBlockWithCatchTarget(); + $elseBlock = $this->createBlockWithCatchTarget(); + + $this->addOp(new Op\Stmt\JumpIf($cond, $ifBlock, $elseBlock, $attrs)); + + $this->block($this->parser->parseNodes($node->stmts, $ifBlock)); + + $this->addOp(new Op\Stmt\Jump($endBlock, $attrs)); + + $this->block($elseBlock); + + if ($node instanceof Node\Stmt\If_) { + foreach ($node->elseifs as $elseIf) { + $this->parseIf($elseIf, $endBlock); + } + if ($node->else) { + $this->block($this->parser->parseNodes($node->else->stmts, $this->block())); + } + $this->addOp(new Op\Stmt\Jump($endBlock, $attrs)); + } + } +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/InlineHTML.php b/lib/PHPCfg/ParserHandler/Stmt/InlineHTML.php new file mode 100644 index 0000000..ec8d827 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/InlineHTML.php @@ -0,0 +1,26 @@ +addOp(new Op\Terminal\Echo_( + $this->parser->readVariable($this->parser->parseExprNode($node->value)), + $this->mapAttributes($node), + )); + } +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/Interface_.php b/lib/PHPCfg/ParserHandler/Stmt/Interface_.php new file mode 100644 index 0000000..3fdcc32 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Interface_.php @@ -0,0 +1,35 @@ +parser->parseTypeNode($node->namespacedName); + $old = $this->parser->currentClass; + $this->parser->currentClass = $name; + $this->addOp(new Op\Stmt\Interface_( + $name, + $this->parser->parseTypeList(...$node->extends), + $this->parser->parseNodes($node->stmts, $this->createBlock()), + $this->parser->parseAttributeGroups(...$node->attrGroups), + $this->mapAttributes($node), + )); + $this->parser->currentClass = $old; + } + +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/Label.php b/lib/PHPCfg/ParserHandler/Stmt/Label.php new file mode 100644 index 0000000..25eca40 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Label.php @@ -0,0 +1,44 @@ +parser->ctx->labels[$node->name->toString()])) { + throw new RuntimeException("Label '{$node->name->toString()}' already defined"); + } + + $labelBlock = $this->createBlockWithParent(); + $this->addOp(new Op\Stmt\Jump($labelBlock, $this->mapAttributes($node))); + + if (isset($this->parser->ctx->unresolvedGotos[$node->name->toString()])) { + /** + * @var Block + * @var array $attributes + */ + foreach ($this->parser->ctx->unresolvedGotos[$node->name->toString()] as [$block, $attributes]) { + $block->children[] = new Op\Stmt\Jump($labelBlock, $attributes); + $labelBlock->addParent($block); + } + unset($this->parser->ctx->unresolvedGotos[$node->name->toString()]); + } + $this->parser->ctx->labels[$node->name->toString()] = $labelBlock; + $this->block($labelBlock); + } +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/Namespace_.php b/lib/PHPCfg/ParserHandler/Stmt/Namespace_.php new file mode 100644 index 0000000..b3dd7f0 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Namespace_.php @@ -0,0 +1,23 @@ +parser->currentNamespace = $node->name; + $this->block($this->parser->parseNodes($node->stmts, $this->block())); + } +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/Property.php b/lib/PHPCfg/ParserHandler/Stmt/Property.php new file mode 100644 index 0000000..57a29c8 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Property.php @@ -0,0 +1,50 @@ +flags & Modifiers::VISIBILITY_MASK; + $static = $node->flags & Modifiers::STATIC; + $readonly = $node->flags & Modifiers::READONLY; + + foreach ($node->props as $prop) { + if ($prop->default) { + $tmp = $this->block(); + $this->block($defaultBlock = $this->createBlock()); + $defaultVar = $this->parser->parseExprNode($prop->default); + $this->block($tmp); + } else { + $defaultVar = null; + $defaultBlock = null; + } + + $this->addOp(new Op\Stmt\Property( + $this->parser->parseExprNode($prop->name), + $visibility, + (bool) $static, + (bool) $readonly, + $this->parser->parseAttributeGroups(...$node->attrGroups), + $this->parser->parseTypeNode($node->type), + $defaultVar, + $defaultBlock, + $this->mapAttributes($node), + )); + } + } +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/Return_.php b/lib/PHPCfg/ParserHandler/Stmt/Return_.php new file mode 100644 index 0000000..1a7cb6f --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Return_.php @@ -0,0 +1,30 @@ +expr) { + $expr = $this->parser->readVariable($this->parser->parseExprNode($node->expr)); + } + $this->addOp(new Op\Terminal\Return_($expr, $this->mapAttributes($node))); + // Dump everything after the return + $this->block($this->createBlockWithParent()); + $this->block()->dead = true; + } +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/Static_.php b/lib/PHPCfg/ParserHandler/Stmt/Static_.php new file mode 100644 index 0000000..1be1386 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Static_.php @@ -0,0 +1,45 @@ +vars as $var) { + $defaultBlock = null; + $defaultVar = null; + if ($var->default) { + $tmp = $this->block(); + $defaultBlock = $this->block($this->createBlockWithParent()); + $defaultVar = $this->parser->parseExprNode($var->default); + $this->block($tmp); + } + $this->addOp(new Op\Terminal\StaticVar( + $this->parser->writeVariable( + new Operand\BoundVariable( + $this->parser->parseExprNode($var->var->name), + true, + Operand\BoundVariable::SCOPE_FUNCTION + ) + ), + $defaultBlock, + $defaultVar, + $this->mapAttributes($node), + )); + } + } +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/Switch_.php b/lib/PHPCfg/ParserHandler/Stmt/Switch_.php new file mode 100644 index 0000000..6a417d8 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Switch_.php @@ -0,0 +1,124 @@ +switchCanUseJumptable($node)) { + $this->compileJumptableSwitch($node); + + return; + } + + // Desugar switch into compare-and-jump sequence + $cond = $this->parser->parseExprNode($node->cond); + $endBlock = $this->createBlockWithCatchTarget(); + $defaultBlock = $endBlock; + /** @var Block|null $prevBlock */ + $prevBlock = null; + foreach ($node->cases as $case) { + $ifBlock = $this->createBlockWithCatchTarget(); + if ($prevBlock && ! $prevBlock->dead) { + $prevBlock->children[] = new Op\Stmt\Jump($ifBlock); + $ifBlock->addParent($prevBlock); + } + + if ($case->cond) { + $caseExpr = $this->parser->parseExprNode($case->cond); + $result = $this->addExpr(new Op\Expr\BinaryOp\Equal( + $this->parser->readVariable($cond), + $this->parser->readVariable($caseExpr), + $this->mapAttributes($case), + )); + + $elseBlock = $this->createBlockWithCatchTarget(); + $this->addOp(new Op\Stmt\JumpIf($result, $ifBlock, $elseBlock)); + $this->block($elseBlock); + } else { + $defaultBlock = $ifBlock; + } + + $prevBlock = $this->parser->parseNodes($case->stmts, $ifBlock); + } + + if ($prevBlock && ! $prevBlock->dead) { + $prevBlock->children[] = new Op\Stmt\Jump($endBlock); + $endBlock->addParent($prevBlock); + } + + $this->addOp(new Op\Stmt\Jump($defaultBlock)); + $this->block($endBlock); + } + + + private function switchCanUseJumptable(Node\Stmt\Switch_ $node): bool + { + foreach ($node->cases as $case) { + if ( + null !== $case->cond + && ! $case->cond instanceof Node\Scalar\LNumber + && ! $case->cond instanceof Node\Scalar\String_ + ) { + return false; + } + } + + return true; + } + + + private function compileJumptableSwitch(Node\Stmt\Switch_ $node): void + { + $cond = $this->parser->readVariable($this->parser->parseExprNode($node->cond)); + $cases = []; + $targets = []; + $endBlock = $this->createBlockWithCatchTarget(); + $defaultBlock = $endBlock; + /** @var null|Block $block */ + $block = null; + foreach ($node->cases as $case) { + $caseBlock = $this->createBlockWithParent(); + if ($block && ! $block->dead) { + // wire up! + $block->children[] = new Op\Stmt\Jump($caseBlock); + $caseBlock->addParent($block); + } + + if ($case->cond) { + $targets[] = $caseBlock; + $cases[] = $this->parser->parseExprNode($case->cond); + } else { + $defaultBlock = $caseBlock; + } + + $block = $this->parser->parseNodes($case->stmts, $caseBlock); + } + $this->addOp(new Op\Stmt\Switch_( + $cond, + $cases, + $targets, + $defaultBlock, + $this->mapAttributes($node), + )); + if ($block && ! $block->dead) { + // wire end of block to endblock + $block->children[] = new Op\Stmt\Jump($endBlock); + $endBlock->addParent($block); + } + $this->block($endBlock); + } +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/TraitUse.php b/lib/PHPCfg/ParserHandler/Stmt/TraitUse.php new file mode 100644 index 0000000..bef7e1c --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/TraitUse.php @@ -0,0 +1,51 @@ +traits as $trait_) { + $traits[] = new Operand\Literal($trait_->toCodeString()); + } + foreach ($node->adaptations as $adaptation) { + if ($adaptation instanceof Node\Stmt\TraitUseAdaptation\Alias) { + $adaptations[] = new Op\TraitUseAdaptation\Alias( + $adaptation->trait != null ? new Operand\Literal($adaptation->trait->toCodeString()) : null, + new Operand\Literal($adaptation->method->name), + $adaptation->newName != null ? new Operand\Literal($adaptation->newName->name) : null, + $adaptation->newModifier, + $this->mapAttributes($adaptation), + ); + } elseif ($adaptation instanceof Node\Stmt\TraitUseAdaptation\Precedence) { + $insteadofs = []; + foreach ($adaptation->insteadof as $insteadof) { + $insteadofs[] = new Operand\Literal($insteadof->toCodeString()); + } + $adaptations[] = new Op\TraitUseAdaptation\Precedence( + $adaptation->trait != null ? new Operand\Literal($adaptation->trait->toCodeString()) : null, + new Operand\Literal($adaptation->method->name), + $insteadofs, + $this->mapAttributes($adaptation), + ); + } + } + $this->addOp(new Op\Stmt\TraitUse($traits, $adaptations, $this->mapAttributes($node))); + } +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/Trait_.php b/lib/PHPCfg/ParserHandler/Stmt/Trait_.php new file mode 100644 index 0000000..535603b --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Trait_.php @@ -0,0 +1,32 @@ +parser->parseTypeNode($node->namespacedName); + $old = $this->parser->currentClass; + $this->parser->currentClass = $name; + $this->addOp(new Op\Stmt\Trait_( + $name, + $this->parser->parseNodes($node->stmts, $this->createBlock()), + $this->parser->parseAttributeGroups(...$node->attrGroups), + $this->mapAttributes($node), + )); + $this->parser->currentClass = $old; + } +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/TryCatch.php b/lib/PHPCfg/ParserHandler/Stmt/TryCatch.php new file mode 100644 index 0000000..a914d1c --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/TryCatch.php @@ -0,0 +1,72 @@ +block(), $catchTarget); + $finally->addParent($body); + $finally->setCatchTarget($this->block()->catchTarget); + $next = new Block($finally); + + foreach ($node->catches as $catch) { + if ($catch->var) { + $var = $this->parser->writeVariable($this->parser->parseExprNode($catch->var)); + } else { + $var = new Operand\NullOperand(); + } + + $catchBody = new Block($body, $finallyTarget); + $finally->addParent($catchBody); + $catchBody2 = $this->parser->parseNodes($catch->stmts, $catchBody); + $catchBody2->children[] = new Op\Stmt\Jump($finally); + + $parsedTypes = []; + foreach ($catch->types as $type) { + $parsedTypes[] = $this->parser->parseTypeNode($type); + } + + $type = new Op\Type\Union( + $parsedTypes, + $this->mapAttributes($catch), + ); + + $catchTarget->addCatch($type, $var, $catchBody); + } + + // parsing body stmts is done after the catches because we want + // to add catch blocks (and finally blocks) as parents of any subblock of the body + $next2 = $this->parser->parseNodes($node->stmts, $body); + $next2->children[] = new Op\Stmt\Jump($finally); + + if ($node->finally != null) { + $nf = $this->parser->parseNodes($node->finally->stmts, $finally); + $nf->children[] = new Op\Stmt\Jump($next); + } else { + $finally->children[] = new Op\Stmt\Jump($next); + } + + $this->addOp(new Op\Stmt\Try_($body, $catchTarget->catches, $finally, $this->mapAttributes($node))); + $this->block($next); + } +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/Unset_.php b/lib/PHPCfg/ParserHandler/Stmt/Unset_.php new file mode 100644 index 0000000..496e0c9 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Unset_.php @@ -0,0 +1,27 @@ +addOp(new Op\Terminal\Unset_( + $this->parser->parseExprList($node->vars, Parser::MODE_WRITE), + $this->mapAttributes($node), + )); + } +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/While_.php b/lib/PHPCfg/ParserHandler/Stmt/While_.php new file mode 100644 index 0000000..b8a2e05 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/While_.php @@ -0,0 +1,34 @@ +createBlockWithCatchTarget(); + $loopBody = $this->createBlockWithCatchTarget(); + $loopEnd = $this->createBlockWithCatchTarget(); + $this->addOp(new Op\Stmt\Jump($loopInit, $this->mapAttributes($node))); + $this->block($loopInit); + $cond = $this->parser->readVariable($this->parser->parseExprNode($node->cond)); + + $this->addOp(new Op\Stmt\JumpIf($cond, $loopBody, $loopEnd, $this->mapAttributes($node))); + + $this->block($this->parser->parseNodes($node->stmts, $loopBody)); + $this->addOp(new Op\Stmt\Jump($loopInit, $this->mapAttributes($node))); + $this->block($loopEnd); + } +} diff --git a/lib/PHPCfg/Printer.php b/lib/PHPCfg/Printer.php index 57ed6be..53572fa 100755 --- a/lib/PHPCfg/Printer.php +++ b/lib/PHPCfg/Printer.php @@ -11,25 +11,24 @@ namespace PHPCfg; +use LogicException; use PHPCfg\Operand\BoundVariable; use PHPCfg\Operand\Literal; +use PHPCfg\Operand\NullOperand; use PHPCfg\Operand\Temporary; use PHPCfg\Operand\Variable; -use PHPCfg\Operand\NullOperand; +use SplObjectStorage; +use SplQueue; abstract class Printer { - /** @var \SplObjectStorage */ - private $varIds; + private SplObjectStorage $varIds; - /** @var \SplQueue */ - private $blockQueue; + private SplQueue $blockQueue; - /** @var \SplObjectStorage */ - private $blocks; + private SplObjectStorage $blocks; - /** @var bool */ - private $renderAttributes; + private bool $renderAttributes; public function __construct(bool $renderAttributes = false) { @@ -37,23 +36,23 @@ public function __construct(bool $renderAttributes = false) $this->reset(); } - abstract public function printScript(Script $script); + abstract public function printScript(Script $script): string; - abstract public function printFunc(Func $func); + abstract public function printFunc(Func $func): string; - protected function reset() + protected function reset(): void { - $this->varIds = new \SplObjectStorage(); - $this->blocks = new \SplObjectStorage(); - $this->blockQueue = new \SplQueue(); + $this->varIds = new SplObjectStorage(); + $this->blocks = new SplObjectStorage(); + $this->blockQueue = new SplQueue(); } - protected function getBlockId(Block $block) + protected function getBlockId(Block $block): int { return $this->blocks[$block]; } - protected function renderOperand(Operand $var) + protected function renderOperand(Operand $var): string { $type = isset($var->type) ? 'type->toString() . '>' : ''; if ($var instanceof Literal) { @@ -77,7 +76,7 @@ protected function renderOperand(Operand $var) case BoundVariable::SCOPE_FUNCTION: return "static<{$prefix}{$var->name->value}>"; default: - throw new \LogicException('Unknown bound variable scope'); + throw new LogicException('Unknown bound variable scope'); } } @@ -106,7 +105,7 @@ protected function renderOperand(Operand $var) return 'UNKNOWN'; } - protected function renderOp(Op $op) + protected function renderOp(Op $op): array { $result = $op->getType(); @@ -226,7 +225,7 @@ protected function renderOp(Op $op) ]; } - protected function renderAssertion(Assertion $assert) + protected function renderAssertion(Assertion $assert): string { $kind = $assert->getKind(); if ($assert->value instanceof Operand) { @@ -241,7 +240,7 @@ protected function renderAssertion(Assertion $assert) return $kind . '(' . implode($combinator, $results) . ')'; } - protected function indent($str, $levels = 1) + protected function indent($str, $levels = 1): string { if ($levels > 1) { $str = $this->indent($str, $levels - 1); @@ -250,7 +249,7 @@ protected function indent($str, $levels = 1) return str_replace("\n", "\n ", $str); } - protected function enqueueBlock(Block $block) + protected function enqueueBlock(Block $block): void { if (! $this->blocks->contains($block)) { $this->blocks[$block] = count($this->blocks) + 1; @@ -273,8 +272,8 @@ protected function render(Func $func) $this->enqueueBlock($func->cfg); } - $renderedOps = new \SplObjectStorage(); - $renderedBlocks = new \SplObjectStorage(); + $renderedOps = new SplObjectStorage(); + $renderedBlocks = new SplObjectStorage(); while ($this->blockQueue->count() > 0) { $block = $this->blockQueue->dequeue(); $ops = []; @@ -317,13 +316,14 @@ protected function renderType(?Op\Type $type): string if ($type instanceof Op\Type\Nullable) { return '?' . $this->renderType($type->subtype); } - if ($type instanceof Op\Type\Union) { + if ($type instanceof Op\Type\Union || $type instanceof Op\Type\Intersection) { $i = 1; $strTypes = ""; + $sep = $type instanceof Op\Type\Union ? '|' : '&'; foreach ($type->subtypes as $subtype) { $strTypes .= $this->renderType($subtype); if ($i < count($type->subtypes)) { - $strTypes .= "|"; + $strTypes .= $sep; } $i++; } @@ -335,7 +335,7 @@ protected function renderType(?Op\Type $type): string if (is_null($type)) { return ''; } - throw new \LogicException("Unknown type rendering: " . get_class($type)); + throw new LogicException("Unknown type rendering: " . get_class($type)); } protected function renderIncludeType(int $type): string @@ -350,7 +350,7 @@ protected function renderIncludeType(int $type): string case 4: return "require_once"; default: - throw new \LogicException("Unknown include type rendering: " . $type); + throw new LogicException("Unknown include type rendering: " . $type); } } diff --git a/lib/PHPCfg/Printer/GraphViz.php b/lib/PHPCfg/Printer/GraphViz.php index 90bd695..9e0b125 100644 --- a/lib/PHPCfg/Printer/GraphViz.php +++ b/lib/PHPCfg/Printer/GraphViz.php @@ -17,10 +17,11 @@ use phpDocumentor\GraphViz\Edge; use phpDocumentor\GraphViz\Graph; use phpDocumentor\GraphViz\Node; +use SplObjectStorage; class GraphViz extends Printer { - protected $options = [ + protected array $options = [ 'graph' => [], 'node' => [ 'shape' => 'rect', @@ -28,7 +29,7 @@ class GraphViz extends Printer 'edge' => [], ]; - protected $graph; + protected Graph $graph; public function __construct(array $options = []) { @@ -36,7 +37,7 @@ public function __construct(array $options = []) $this->options = $options + $this->options; } - public function printScript(Script $script) + public function printScript(Script $script): string { $i = 0; $graph = $this->createGraph(); @@ -45,18 +46,18 @@ public function printScript(Script $script) $this->printFuncWithHeader($func, $graph, 'func_' . ++$i . '_'); } - return $graph; + return (string) $graph; } - public function printFunc(Func $func) + public function printFunc(Func $func): string { $graph = $this->createGraph(); $this->printFuncInfo($func, $graph, ''); - return $graph; + return (string) $graph; } - public function printVars(Func $func) + public function printVars(Func $func): string { $graph = Graph::create('vars'); foreach ($this->options['graph'] as $name => $value) { @@ -64,7 +65,7 @@ public function printVars(Func $func) $graph->{$setter}($value); } $rendered = $this->render($func->cfg); - $nodes = new \SplObjectStorage(); + $nodes = new SplObjectStorage(); foreach ($rendered['varIds'] as $var) { if (empty($var->ops) && empty($var->usages)) { continue; @@ -97,10 +98,10 @@ public function printVars(Func $func) } } - return $graph; + return (string) $graph; } - protected function printFuncWithHeader(Func $func, Graph $graph, $prefix) + protected function printFuncWithHeader(Func $func, Graph $graph, $prefix): void { $name = $func->getScopedName(); $header = $this->createNode( @@ -114,7 +115,7 @@ protected function printFuncWithHeader(Func $func, Graph $graph, $prefix) $graph->link($edge); } - protected function getEdgeTypeColor(string $type) + protected function getEdgeTypeColor(string $type): string { static $colors = [ "#D54E4E", @@ -140,10 +141,10 @@ protected function getEdgeTypeColor(string $type) return $edgeColors[$type]; } - protected function printFuncInto(Func $func, Graph $graph, $prefix) + protected function printFuncInto(Func $func, Graph $graph, $prefix): Node { $rendered = $this->render($func); - $nodes = new \SplObjectStorage(); + $nodes = new SplObjectStorage(); foreach ($rendered['blocks'] as $block) { $blockId = $rendered['blockIds'][$block]; $ops = $rendered['blocks'][$block]; @@ -196,7 +197,7 @@ protected function indent($str, $levels = 1): string return str_replace(["\n", '\\l'], '\\l ', $str); } - private function createGraph() + private function createGraph(): Graph { $graph = Graph::create('cfg'); foreach ($this->options['graph'] as $name => $value) { @@ -207,7 +208,7 @@ private function createGraph() return $graph; } - private function createNode($id, $content) + private function createNode($id, $content): Node { $node = new Node($id, $content); foreach ($this->options['node'] as $name => $value) { @@ -217,7 +218,7 @@ private function createNode($id, $content) return $node; } - private function createEdge(Node $from, Node $to) + private function createEdge(Node $from, Node $to): Edge { $edge = new Edge($from, $to); foreach ($this->options['edge'] as $name => $value) { diff --git a/lib/PHPCfg/Printer/Text.php b/lib/PHPCfg/Printer/Text.php index 7b48c99..533616e 100755 --- a/lib/PHPCfg/Printer/Text.php +++ b/lib/PHPCfg/Printer/Text.php @@ -17,7 +17,7 @@ class Text extends Printer { - public function printScript(Script $script) + public function printScript(Script $script): string { $output = ''; $output .= $this->printFunc($script->main); @@ -30,7 +30,7 @@ public function printScript(Script $script) return $output; } - public function printFunc(Func $func) + public function printFunc(Func $func): string { $rendered = $this->render($func); $output = ''; @@ -64,7 +64,7 @@ public function printFunc(Func $func) return $output; } - public function printVars(Func $func) + public function printVars(Func $func): string { $rendered = $this->render($func); $output = ''; diff --git a/lib/PHPCfg/Script.php b/lib/PHPCfg/Script.php index 551bf88..0f3d307 100644 --- a/lib/PHPCfg/Script.php +++ b/lib/PHPCfg/Script.php @@ -14,8 +14,14 @@ class Script { /** @var Func[] */ - public $functions = []; + public array $functions = []; + + public Func $main; + + public bool $strict_types = false; + + public int $ticks = 0; + + public string $encoding = 'ISO-8859-1'; - /** @var Func */ - public $main; } diff --git a/lib/PHPCfg/Traverser.php b/lib/PHPCfg/Traverser.php index d0ae9f1..a90e93d 100755 --- a/lib/PHPCfg/Traverser.php +++ b/lib/PHPCfg/Traverser.php @@ -11,12 +11,14 @@ namespace PHPCfg; +use RuntimeException; +use SplObjectStorage; + class Traverser { - /** @var \SplObjectStorage */ - private $seen; + private ?SplObjectStorage $seen = null; - private $visitors = []; + private array $visitors = []; public function addVisitor(Visitor $visitor) { @@ -33,15 +35,16 @@ public function traverse(Script $script) $this->event('leaveScript', [$script]); } - public function traverseFunc(Func $func) + private function traverseFunc(Func $func) { - $this->seen = new \SplObjectStorage(); + $oldSeen = $this->seen; + $this->seen = new SplObjectStorage(); $this->event('enterFunc', [$func]); $block = $func->cfg; if (null !== $block) { $result = $this->traverseBlock($block, null); if ($result === Visitor::REMOVE_BLOCK) { - throw new \RuntimeException('Cannot remove function start block'); + throw new RuntimeException('Cannot remove function start block'); } if (null !== $result) { $block = $result; @@ -49,7 +52,7 @@ public function traverseFunc(Func $func) $func->cfg = $block; } $this->event('leaveFunc', [$func]); - $this->seen = null; + $this->seen = $oldSeen; } private function traverseBlock(Block $block, ?Block $prior = null) @@ -83,7 +86,7 @@ private function traverseBlock(Block $block, ?Block $prior = null) // Revisit the ith block again --$j; } elseif (null !== $result) { - throw new \RuntimeException('Unknown return from visitor: ' . gettype($result)); + throw new RuntimeException('Unknown return from visitor: ' . gettype($result)); } } @@ -103,7 +106,7 @@ private function traverseBlock(Block $block, ?Block $prior = null) // Revisit the ith node again --$i; } elseif (null !== $result && $result !== $op) { - throw new \RuntimeException('Unknown return from visitor: ' . gettype($result)); + throw new RuntimeException('Unknown return from visitor: ' . gettype($result)); } } $block->children = $children; @@ -111,13 +114,14 @@ private function traverseBlock(Block $block, ?Block $prior = null) return $this->event('leaveBlock', [$block, $prior]); } - private function event($name, array $args) + private function event(string $name, array $args): mixed { foreach ($this->visitors as $visitor) { - $return = call_user_func_array([$visitor, $name], $args); + $return = $visitor->$name(...$args); if (null !== $return) { return $return; } } + return null; } } diff --git a/lib/PHPCfg/Visitor.php b/lib/PHPCfg/Visitor.php index d19af3d..b70af04 100755 --- a/lib/PHPCfg/Visitor.php +++ b/lib/PHPCfg/Visitor.php @@ -17,21 +17,21 @@ interface Visitor public const REMOVE_BLOCK = -2; - public function enterScript(Script $script); + public function enterScript(Script $script): void; - public function leaveScript(Script $script); + public function leaveScript(Script $script): void; - public function enterFunc(Func $func); + public function enterFunc(Func $func): void; - public function leaveFunc(Func $func); + public function leaveFunc(Func $func): void; - public function enterBlock(Block $block, ?Block $prior = null); + public function enterBlock(Block $block, ?Block $prior = null): void; - public function enterOp(Op $op, Block $block); + public function enterOp(Op $op, Block $block): void; - public function leaveOp(Op $op, Block $block); + public function leaveOp(Op $op, Block $block): Op|int|null; - public function leaveBlock(Block $block, ?Block $prior = null); + public function leaveBlock(Block $block, ?Block $prior = null): Block|int|null; - public function skipBlock(Block $block, ?Block $prior = null); + public function skipBlock(Block $block, ?Block $prior = null): void; } diff --git a/lib/PHPCfg/Visitor/CallFinder.php b/lib/PHPCfg/Visitor/CallFinder.php index 1a3f98f..95b862b 100644 --- a/lib/PHPCfg/Visitor/CallFinder.php +++ b/lib/PHPCfg/Visitor/CallFinder.php @@ -79,18 +79,18 @@ public function getFuncCalls(): array return $this->funcCalls; } - public function enterFunc(Func $func) + public function enterFunc(Func $func): void { $this->funcStack[] = $this->func; $this->func = $func; } - public function leaveFunc(Func $func) + public function leaveFunc(Func $func): void { $this->func = array_pop($this->funcStack); } - public function enterOp(Op $op, Block $block) + public function enterOp(Op $op, Block $block): void { if ($op instanceof Op\Expr\FuncCall) { $this->funcCalls[] = [$op, $this->func]; diff --git a/lib/PHPCfg/Visitor/DebugVisitor.php b/lib/PHPCfg/Visitor/DebugVisitor.php index 90ef2d2..3131bf0 100644 --- a/lib/PHPCfg/Visitor/DebugVisitor.php +++ b/lib/PHPCfg/Visitor/DebugVisitor.php @@ -16,11 +16,11 @@ use PHPCfg\Op; use PHPCfg\Script; use PHPCfg\Visitor; +use SplObjectStorage; class DebugVisitor implements Visitor { - /** @var \SplObjectStorage */ - protected $blocks; + protected ?SplObjectStorage $blocks; public function enterScript(Script $script) { @@ -59,7 +59,7 @@ public function skipBlock(Block $block, ?Block $prior = null) public function enterFunc(Func $func) { - $this->blocks = new \SplObjectStorage(); + $this->blocks = new SplObjectStorage(); echo 'Enter Func ' . $func->getScopedName() . "\n"; } diff --git a/lib/PHPCfg/Visitor/DeclarationFinder.php b/lib/PHPCfg/Visitor/DeclarationFinder.php index 607663f..9bf124c 100755 --- a/lib/PHPCfg/Visitor/DeclarationFinder.php +++ b/lib/PHPCfg/Visitor/DeclarationFinder.php @@ -17,49 +17,49 @@ class DeclarationFinder extends AbstractVisitor { - protected $traits = []; + protected array $traits = []; - protected $classes = []; + protected array $classes = []; - protected $methods = []; + protected array $methods = []; - protected $functions = []; + protected array $functions = []; - protected $interfaces = []; + protected array $interfaces = []; - protected $constants = []; + protected array $constants = []; - public function getConstants() + public function getConstants(): array { return $this->constants; } - public function getTraits() + public function getTraits(): array { return $this->traits; } - public function getClasses() + public function getClasses(): array { return $this->classes; } - public function getMethods() + public function getMethods(): array { return $this->methods; } - public function getFunctions() + public function getFunctions(): array { return $this->functions; } - public function getInterfaces() + public function getInterfaces(): array { return $this->interfaces; } - public function enterOp(Op $op, Block $block) + public function enterOp(Op $op, Block $block): void { if ($op instanceof Op\Stmt\Trait_) { $this->traits[] = $op; diff --git a/lib/PHPCfg/Visitor/Simplifier.php b/lib/PHPCfg/Visitor/Simplifier.php index 8c38f13..406fb13 100644 --- a/lib/PHPCfg/Visitor/Simplifier.php +++ b/lib/PHPCfg/Visitor/Simplifier.php @@ -16,34 +16,32 @@ use PHPCfg\Func; use PHPCfg\Op; use PHPCfg\Operand; +use SplObjectStorage; class Simplifier extends AbstractVisitor { - /** @var \SplObjectStorage */ - protected $removed; + protected ?SplObjectStorage $removed; - /** @var \SplObjectStorage */ - protected $recursionProtection; + protected ?SplObjectStorage $recursionProtection; - /** @var \SplObjectStorage */ - protected $trivialPhiCandidates; + protected ?SplObjectStorage $trivialPhiCandidates; - public function enterFunc(Func $func) + public function enterFunc(Func $func): void { - $this->removed = new \SplObjectStorage(); - $this->recursionProtection = new \SplObjectStorage(); + $this->removed = new SplObjectStorage(); + $this->recursionProtection = new SplObjectStorage(); } - public function leaveFunc(Func $func) + public function leaveFunc(Func $func): void { // Remove trivial PHI functions if ($func->cfg) { - $this->trivialPhiCandidates = new \SplObjectStorage(); + $this->trivialPhiCandidates = new SplObjectStorage(); $this->removeTrivialPhi($func->cfg); } } - public function enterOp(Op $op, Block $block) + public function enterOp(Op $op, Block $block): void { if ($this->recursionProtection->contains($op)) { return; @@ -137,10 +135,10 @@ public function enterOp(Op $op, Block $block) $this->recursionProtection->detach($op); } - private function removeTrivialPhi(Block $block) + private function removeTrivialPhi(Block $block): void { - $toReplace = new \SplObjectStorage(); - $replaced = new \SplObjectStorage(); + $toReplace = new SplObjectStorage(); + $replaced = new SplObjectStorage(); $toReplace->attach($block); while ($toReplace->count() > 0) { foreach ($toReplace as $block) { @@ -182,7 +180,7 @@ private function removeTrivialPhi(Block $block) } } - private function tryRemoveTrivialPhi(Op\Phi $phi, Block $block) + private function tryRemoveTrivialPhi(Op\Phi $phi, Block $block): bool { if (count($phi->vars) > 1) { return false; @@ -199,10 +197,10 @@ private function tryRemoveTrivialPhi(Op\Phi $phi, Block $block) return true; } - private function replaceVariables(Operand $from, Operand $to, Block $block) + private function replaceVariables(Operand $from, Operand $to, Block $block): void { - $toReplace = new \SplObjectStorage(); - $replaced = new \SplObjectStorage(); + $toReplace = new SplObjectStorage(); + $replaced = new SplObjectStorage(); $toReplace->attach($block); while ($toReplace->count() > 0) { foreach ($toReplace as $block) { @@ -236,7 +234,7 @@ private function replaceVariables(Operand $from, Operand $to, Block $block) } } - private function replaceOpVariable(Operand $from, Operand $to, Op $op) + private function replaceOpVariable(Operand $from, Operand $to, Op $op): void { foreach ($op->getVariableNames() as $name => $var) { if (null === $var) { diff --git a/lib/PHPCfg/Visitor/VariableFinder.php b/lib/PHPCfg/Visitor/VariableFinder.php index e998ad6..d3f0688 100644 --- a/lib/PHPCfg/Visitor/VariableFinder.php +++ b/lib/PHPCfg/Visitor/VariableFinder.php @@ -14,29 +14,30 @@ use PHPCfg\AbstractVisitor; use PHPCfg\Block; use PHPCfg\Op; +use SplObjectStorage; class VariableFinder extends AbstractVisitor { - protected $variables; + protected SplObjectStorage $variables; public function __construct() { - $this->variables = new \SplObjectStorage(); + $this->variables = new SplObjectStorage(); } - public function getVariables() + public function getVariables(): SplObjectStorage { return $this->variables; } - public function enterBlock(Block $block, ?Block $prior = null) + public function enterBlock(Block $block, ?Block $prior = null): void { foreach ($block->phi as $phi) { $this->enterOp($phi, $block); } } - public function enterOp(Op $op, Block $block) + public function enterOp(Op $op, Block $block): void { foreach ($op->getVariableNames() as $var) { if (! is_array($var)) { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index cbd6d51..1d2c9ba 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -5,13 +5,14 @@ stopOnFailure="false" bootstrap="./vendor/autoload.php"> - + ./test/ - ./lib/PHPCfg/ + ./lib/ + ./src/ \ No newline at end of file diff --git a/src/Cli/BaseCommand.php b/src/Cli/BaseCommand.php new file mode 100644 index 0000000..bdc20c8 --- /dev/null +++ b/src/Cli/BaseCommand.php @@ -0,0 +1,35 @@ +createForNewestSupportedVersion()); + + $traverser = new Traverser(); + + if ($optimize) { + $traverser->addVisitor(new Visitor\Simplifier()); + } + + $script = $parser->parse($code, $file); + $traverser->traverse($script); + return $script; + } +} diff --git a/src/Cli/DotCommand.php b/src/Cli/DotCommand.php new file mode 100644 index 0000000..52df589 --- /dev/null +++ b/src/Cli/DotCommand.php @@ -0,0 +1,53 @@ +argument('', 'File to process') + ->option('-n|--no-optimize', 'Disable Optimizers') + ->option('-o|--output', 'Output File', null, '-') + ; + } + + public function execute($file, $optimize, $output) + { + $io = $this->app()->io(); + $color = new Color(); + + if (file_exists($file)) { + $code = file_get_contents($file); + } else { + $io->write($color->error("Unknown file $file")); + return 1; + } + + $script = $this->exec($file, $code, $optimize); + + $dumper = new Printer\GraphViz(); + $result = $dumper->printScript($script); + if ($output === '-') { + $io->write($result); + } else { + file_put_contents($output, $result); + $io->write("Saved to {$output}", true); + } + } +} diff --git a/src/Cli/PrintCommand.php b/src/Cli/PrintCommand.php new file mode 100644 index 0000000..34be8a9 --- /dev/null +++ b/src/Cli/PrintCommand.php @@ -0,0 +1,46 @@ +argument('', 'File to process') + ->option('-n|--no-optimize', 'Disable Optimizers', 'boolval', false) + ->option('-a|--attributes', 'Render Attributes', 'boolval', false) + ; + } + + public function execute($file, $optimize, $attributes) + { + $io = $this->app()->io(); + $color = new Color(); + + if (file_exists($file)) { + $code = file_get_contents($file); + } else { + $io->write($color->error("Unknown file $file")); + return 1; + } + + $script = $this->exec($file, $code, $optimize); + + $dumper = new Printer\Text($attributes); + $io->write($dumper->printScript($script), true); + } +} diff --git a/src/Cli/RunTestCommand.php b/src/Cli/RunTestCommand.php new file mode 100644 index 0000000..90d6537 --- /dev/null +++ b/src/Cli/RunTestCommand.php @@ -0,0 +1,47 @@ +argument('', 'Name of test to run (without .test suffix)') + ; + } + + public function execute($test) + { + $io = $this->app()->io(); + $color = new Color(); + + $file = __DIR__ . '/../../test/code/' . $test . '.test'; + + if (file_exists($file)) { + [$code] = explode('-----', file_get_contents($file), 2); + } else { + $io->write($color->error("Unknown file $file")); + return 1; + } + + $script = $this->exec($file, $code, true); + + $dumper = new Printer\Text(); + $io->write($dumper->printScript($script), true); + } +} diff --git a/test/PHPCfg/ParserTest.php b/test/CodeTest.php similarity index 82% rename from test/PHPCfg/ParserTest.php rename to test/CodeTest.php index 2823c36..aabf85d 100755 --- a/test/PHPCfg/ParserTest.php +++ b/test/CodeTest.php @@ -13,10 +13,15 @@ use PhpParser; use PhpParser\ParserFactory; -use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Attributes\CoversNothing; use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use RuntimeException; -class ParserTest extends TestCase +#[CoversNothing] +class CodeTest extends TestCase { #[DataProvider('provideTestParseAndDump')] public function testParseAndDump($code, $expectedDump) @@ -32,7 +37,7 @@ public function testParseAndDump($code, $expectedDump) $script = $parser->parse($code, 'foo.php'); $traverser->traverse($script); $result = $printer->printScript($script); - } catch (\RuntimeException $e) { + } catch (RuntimeException $e) { $result = $e->getMessage(); } @@ -44,10 +49,10 @@ public function testParseAndDump($code, $expectedDump) public static function provideTestParseAndDump() { - $dir = __DIR__ . '/../code'; - $iter = new \RecursiveIteratorIterator( - new \RecursiveDirectoryIterator($dir), - \RecursiveIteratorIterator::LEAVES_ONLY, + $dir = __DIR__ . '/code'; + $iter = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($dir), + RecursiveIteratorIterator::LEAVES_ONLY, ); foreach ($iter as $file) { diff --git a/test/PHPCfg/MagicStringResolverTest.php b/test/PHPCfg/MagicStringResolverTest.php deleted file mode 100644 index acb3ece..0000000 --- a/test/PHPCfg/MagicStringResolverTest.php +++ /dev/null @@ -1,63 +0,0 @@ -astParser = (new ParserFactory())->createForNewestSupportedVersion(); - } - - public function testIgnoresInvalidParamTypeInDocComment() - { - $doccomment = <<<'DOC' - /** - * @param Foo\foo bar - */ - DOC; - - $code = <<<'DOC' - astParser->parse($code); - $traverser = new NodeTraverser(); - $traverser->addVisitor(new NameResolver()); - $traverser->addVisitor(new MagicStringResolver()); - $traverser->traverse($ast); - - $this->assertEquals($doccomment, $ast[0]->stmts[0]->stmts[0]->getDocComment()->getText()); - $this->assertEquals(9, $ast[0]->stmts[0]->stmts[0]->stmts[0]->exprs[0]->value); - } -} diff --git a/test/code/and.test b/test/code/and.test index ca67bd6..5558039 100755 --- a/test/code/and.test +++ b/test/code/and.test @@ -23,8 +23,8 @@ Block#2 target: Block#3 Block#3 - Parent: Block#1 Parent: Block#2 + Parent: Block#1 Var#4 = Phi(LITERAL(false), Var#3) Expr_Assign var: Var#5<$x> diff --git a/test/code/cast.test b/test/code/cast.test new file mode 100644 index 0000000..0201bba --- /dev/null +++ b/test/code/cast.test @@ -0,0 +1,14 @@ + + expr: Var#1 + result: Var#3 + Terminal_Return diff --git a/test/code/intersection_type.test b/test/code/intersection_type.test new file mode 100644 index 0000000..8fea55c --- /dev/null +++ b/test/code/intersection_type.test @@ -0,0 +1,17 @@ + + Terminal_Return + +Function 'foo': mixed +Block#1 + Expr_Param + declaredType: array&string&null + name: LITERAL('string') + result: Var#1<$string> + Terminal_Return + diff --git a/test/code/match.test b/test/code/match.test new file mode 100644 index 0000000..2d302d2 --- /dev/null +++ b/test/code/match.test @@ -0,0 +1,54 @@ + 'foo', + 5 => 'bar', + 10 => 'baz' . 'buz', +}; +----- +Block#1 + Stmt_MatchTable + cond: LITERAL(10) + armConditions[0]: LITERAL(1) + armConditions[1]: LITERAL(5) + armConditions[2]: LITERAL(10) + armBlocks[0]: Block#2 + armBlocks[1]: Block#3 + armBlocks[2]: Block#4 + +Block#2 + Parent: Block#1 + Expr_Assign + var: Var#1 + expr: LITERAL('foo') + result: Var#2 + Stmt_Jump + target: Block#5 + +Block#3 + Parent: Block#1 + Expr_Assign + var: Var#3 + expr: LITERAL('bar') + result: Var#4 + Stmt_Jump + target: Block#5 + +Block#4 + Parent: Block#1 + Expr_BinaryOp_Concat + left: LITERAL('baz') + right: LITERAL('buz') + result: Var#5 + Stmt_Jump + target: Block#5 + +Block#5 + Parent: Block#2 + Parent: Block#3 + Parent: Block#4 + Var#6 = Phi(Var#1, Var#3, Var#5) + Terminal_Echo + expr: Var#6 + Terminal_Return + + diff --git a/test/code/match2.test b/test/code/match2.test new file mode 100644 index 0000000..8a8cb8f --- /dev/null +++ b/test/code/match2.test @@ -0,0 +1,83 @@ + 'foo', + 5 => 'bar', + 5 + 5 => 'baz' . 'buz', +}; +----- +Block#1 + Expr_BinaryOp_Identical + left: LITERAL(1) + right: LITERAL(10) + result: Var#1 + Stmt_JumpIf + cond: Var#1 + if: Block#2 + else: Block#3 + +Block#2 + Parent: Block#1 + Expr_Assign + var: Var#2 + expr: LITERAL('foo') + result: Var#3 + Stmt_Jump + target: Block#4 + +Block#3 + Parent: Block#1 + Expr_BinaryOp_Identical + left: LITERAL(5) + right: LITERAL(10) + result: Var#4 + Stmt_JumpIf + cond: Var#4 + if: Block#5 + else: Block#6 + +Block#4 + Parent: Block#2 + Parent: Block#5 + Parent: Block#7 + Var#5 = Phi(Var#2, Var#6, Var#7) + Terminal_Echo + expr: Var#5 + Terminal_Return + +Block#5 + Parent: Block#3 + Expr_Assign + var: Var#6 + expr: LITERAL('bar') + result: Var#8 + Stmt_Jump + target: Block#4 + +Block#6 + Parent: Block#3 + Expr_BinaryOp_Plus + left: LITERAL(5) + right: LITERAL(5) + result: Var#9 + Expr_BinaryOp_Identical + left: Var#9 + right: LITERAL(10) + result: Var#10 + Stmt_JumpIf + cond: Var#10 + if: Block#7 + else: Block#8 + +Block#7 + Parent: Block#6 + Expr_BinaryOp_Concat + left: LITERAL('baz') + right: LITERAL('buz') + result: Var#7 + Stmt_Jump + target: Block#4 + +Block#8 + Parent: Block#6 + Terminal_MatchError + cond: LITERAL(10) diff --git a/test/code/or.test b/test/code/or.test index 8f57529..80d537b 100755 --- a/test/code/or.test +++ b/test/code/or.test @@ -12,8 +12,8 @@ Block#1 else: Block#3 Block#2 - Parent: Block#1 Parent: Block#3 + Parent: Block#1 Var#2 = Phi(LITERAL(true), Var#3) Expr_Assign var: Var#4<$x> diff --git a/test/code/type_assert.test b/test/code/type_assert.test index 08d75ec..fcbc950 100755 --- a/test/code/type_assert.test +++ b/test/code/type_assert.test @@ -17,49 +17,61 @@ Block#1 Block#2 Parent: Block#1 - Parent: Block#3 - Var#3 = Phi(LITERAL(true), Var#4) - Stmt_JumpIf - cond: Var#3 - if: Block#4 - else: Block#5 + Expr_Assertion + expr: Var#1<$a> + result: Var#3<$a> + Stmt_Jump + target: Block#4 Block#3 Parent: Block#1 + Expr_Assertion + expr: Var#1<$a> + result: Var#4<$a> Expr_FuncCall name: LITERAL('is_float') - args[0]: Var#1<$a> + args[0]: Var#4<$a> result: Var#5 Expr_Cast_Bool expr: Var#5 - result: Var#4 + result: Var#6 Stmt_Jump - target: Block#2 + target: Block#4 Block#4 + Parent: Block#3 Parent: Block#2 + Var#7 = Phi(LITERAL(true), Var#6) + Var#8<$a> = Phi(Var#4<$a>, Var#3<$a>) + Stmt_JumpIf + cond: Var#7 + if: Block#5 + else: Block#6 + +Block#5 + Parent: Block#4 Expr_Assertion<(type(LITERAL('int'))|type(LITERAL('float')))> - expr: Var#1<$a> - result: Var#6<$a> + expr: Var#8<$a> + result: Var#9<$a> Terminal_Echo - expr: Var#6<$a> + expr: Var#9<$a> Stmt_Jump - target: Block#6 + target: Block#7 -Block#5 - Parent: Block#2 +Block#6 + Parent: Block#4 Expr_Assertion - expr: Var#1<$a> - result: Var#7<$a> + expr: Var#8<$a> + result: Var#10<$a> Stmt_Jump - target: Block#6 + target: Block#7 -Block#6 - Parent: Block#4 +Block#7 Parent: Block#5 - Var#8<$a> = Phi(Var#6<$a>, Var#7<$a>) + Parent: Block#6 + Var#11<$a> = Phi(Var#9<$a>, Var#10<$a>) Expr_FuncCall name: LITERAL('var_dump') - args[0]: Var#8<$a> - result: Var#9 + args[0]: Var#11<$a> + result: Var#12 Terminal_Return \ No newline at end of file diff --git a/test/code/unary.test b/test/code/unary.test new file mode 100644 index 0000000..3f7a71a --- /dev/null +++ b/test/code/unary.test @@ -0,0 +1,30 @@ + + expr: Var#1 + result: Var#3 + Expr_UnaryMinus + expr: LITERAL(2) + result: Var#4 + Expr_Assign + var: Var#5<$b> + expr: Var#4 + result: Var#6 + Expr_BitwiseNot + expr: LITERAL(3) + result: Var#7 + Expr_Assign + var: Var#8<$c> + expr: Var#7 + result: Var#9 + Terminal_Return \ No newline at end of file diff --git a/test/code/while.test b/test/code/while.test new file mode 100644 index 0000000..d19ca98 --- /dev/null +++ b/test/code/while.test @@ -0,0 +1,35 @@ + + Stmt_Function<'gen_zero_four'> + Terminal_Return + +Function 'gen_one_to_three': mixed +Block#1 + Expr_Assign + var: Var#1<$i> + expr: LITERAL(1) + result: Var#2 + Stmt_Jump + target: Block#2 + +Block#2 + Parent: Block#1 + Parent: Block#5 + Var#3<$i> = Phi(Var#1<$i>, Var#4<$i>) + Expr_BinaryOp_SmallerOrEqual + left: Var#3<$i> + right: LITERAL(3) + result: Var#5 + Stmt_JumpIf + cond: Var#5 + if: Block#3 + else: Block#4 + +Block#3 + Parent: Block#2 + Expr_Yield + key: Var#3<$i> + result: Var#6 + Stmt_Jump + target: Block#5 + +Block#4 + Parent: Block#2 + Terminal_Return + +Block#5 + Parent: Block#3 + Expr_BinaryOp_Plus + left: Var#3<$i> + right: LITERAL(1) + result: Var#7 + Expr_Assign + var: Var#4<$i> + expr: Var#7 + result: Var#8 + Stmt_Jump + target: Block#2 + +Function 'gen_zero_four': mixed +Block#1 + Expr_Yield + key: LITERAL(0) + result: Var#1 + Expr_FuncCall + name: LITERAL('gen_one_to_three') + result: Var#2 + Expr_YieldFrom + expr: Var#2 + result: Var#3 + Expr_Yield + key: LITERAL(4) + result: Var#4 + Terminal_Return diff --git a/test/PHPCfg/AttributesTest.php b/test/integration/ParserAttributesTest.php similarity index 96% rename from test/PHPCfg/AttributesTest.php rename to test/integration/ParserAttributesTest.php index 05ac937..dc95c98 100755 --- a/test/PHPCfg/AttributesTest.php +++ b/test/integration/ParserAttributesTest.php @@ -12,9 +12,12 @@ namespace PHPCfg; use PhpParser\ParserFactory; +use PHPUnit\Framework\Attributes\CoversNothing; use PHPUnit\Framework\TestCase; +use RuntimeException; -class AttributesTest extends TestCase +#[CoversNothing] +class ParserAttributesTest extends TestCase { public function testDefault() { @@ -49,7 +52,7 @@ function foo(\$a) { $script = $parser->parse($code, 'foo.php'); $traverser->traverse($script); $result = $printer->printScript($script); - } catch (\RuntimeException $e) { + } catch (RuntimeException $e) { $result = $e->getMessage(); } @@ -163,7 +166,7 @@ function foowithattribute(\$a) { $script = $parser->parse($code, 'foo.php'); $traverser->traverse($script); $result = $printer->printScript($script); - } catch (\RuntimeException $e) { + } catch (RuntimeException $e) { $result = $e->getMessage(); } diff --git a/test/unit/AssertionTest.php b/test/unit/AssertionTest.php new file mode 100644 index 0000000..f06e89e --- /dev/null +++ b/test/unit/AssertionTest.php @@ -0,0 +1,70 @@ +expectException(RuntimeException::class); + $this->expectExceptionMessage("Empty value supplied for Assertion"); + + new Assertion([]); + } + + public function testInvalidAssertion() + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage("Invalid array key supplied for Assertion"); + + new Assertion([123]); + } + + public function testInvalidModeArray() + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage("Invalid mode supplied for Assertion"); + + new Assertion([new Assertion(new class extends Operand {})], 99); + } + + public function testValidModeArray() + { + $assert = new Assertion([new Assertion(new class extends Operand {})], Assertion::MODE_INTERSECTION); + $this->assertEquals(Assertion::MODE_INTERSECTION, $assert->mode); + } + + public function testGetKind() + { + $assert = new Assertion([new Assertion(new class extends Operand {})], Assertion::MODE_INTERSECTION); + $this->assertEquals('', $assert->getKind()); + } + + public function testInvalidMode() + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage("Invalid mode supplied for Assertion"); + + new class (new class extends Operand {}, 99) extends Assertion { + public function __construct(Operand $op, int $mode) + { + $this->setMode($mode); + } + }; + } + +} diff --git a/test/unit/AstVisitor/LoopResolverTest.php b/test/unit/AstVisitor/LoopResolverTest.php new file mode 100644 index 0000000..233ff9d --- /dev/null +++ b/test/unit/AstVisitor/LoopResolverTest.php @@ -0,0 +1,144 @@ +astParser = (new ParserFactory())->createForNewestSupportedVersion(); + $this->traverser = new NodeTraverser(); + // Always requires name resolution first + $this->traverser->addVisitor(new LoopResolver()); + $this->printer = new Standard(); + } + + public static function provideTestResolve(): array + { + return [ + [<<<'DOC' + do { + break; + } while (true); + DOC, + <<<'DOC' + do { + goto compiled_label_%s_1; + compiled_label_%s_0: + } while (true); + compiled_label_%s_1: + DOC], + [<<<'DOC' + switch($foo) { + case 1: + break; + default: + } + DOC, + <<<'DOC' + switch ($foo) { + case 1: + goto compiled_label_%s_0; + default: + } + compiled_label_%s_0: + DOC], + [<<<'DOC' + while(true) { + continue; + break; + } + DOC, + <<<'DOC' + while (true) { + goto compiled_label_%s_0; + goto compiled_label_%s_1; + compiled_label_%s_0: + } + compiled_label_%s_1: + DOC], + [<<<'DOC' + while(true) { + while(true) { + continue 2; + break 2; + continue; + break; + } + } + DOC, + <<<'DOC' + while (true) { + while (true) { + goto compiled_label_%s_0; + goto compiled_label_%s_1; + goto compiled_label_%s_2; + goto compiled_label_%s_3; + compiled_label_%s_2: + } + compiled_label_%s_3: + compiled_label_%s_0: + } + compiled_label_%s_1: + DOC], + ]; + } + + #[DataProvider('provideTestResolve')] + public function testResolve($code, $expect) + { + $ast = $this->astParser->parse('traverser->traverse($ast); + $this->assertStringMatchesFormat($expect, $this->printer->prettyPrint($new)); + } + + public function testBreakTooLarge() + { + $code = 'astParser->parse($code); + $this->expectException(LogicException::class); + $this->expectExceptionMessage("Too high of a count for Stmt_Break"); + + $this->traverser->traverse($ast); + } + + public function testBreakTypeWrong() + { + $code = 'astParser->parse($code); + $this->expectException(LogicException::class); + $this->expectExceptionMessage("Unimplemented Node Value Type"); + + $this->traverser->traverse($ast); + } +} diff --git a/test/unit/AstVisitor/MagicStringResolverTest.php b/test/unit/AstVisitor/MagicStringResolverTest.php new file mode 100644 index 0000000..7d49b1b --- /dev/null +++ b/test/unit/AstVisitor/MagicStringResolverTest.php @@ -0,0 +1,243 @@ +astParser = (new ParserFactory())->createForNewestSupportedVersion(); + $this->traverser = new NodeTraverser(); + // Always requires name resolution first + $this->traverser->addVisitor(new NameResolver()); + $this->traverser->addVisitor(new MagicStringResolver()); + } + + public function testParsesLineNumberCorrectly() + { + $code = <<<'DOC' + astParser->parse($code); + $this->traverser->traverse($ast); + + $this->assertEquals(9, $ast[0]->stmts[0]->stmts[0]->stmts[0]->exprs[0]->value); + } + + public function testParsesMethod() + { + $code = <<<'DOC' + astParser->parse($code); + $this->traverser->traverse($ast); + + $this->assertEquals("Foo\Test::test", $ast[0]->stmts[0]->stmts[0]->stmts[0]->exprs[0]->value); + } + + public function testParsesClass() + { + $code = <<<'DOC' + astParser->parse($code); + $this->traverser->traverse($ast); + + $this->assertEquals("Foo\Test", $ast[0]->stmts[0]->stmts[0]->stmts[0]->exprs[0]->value); + } + + public function testParsesFunction() + { + $code = <<<'DOC' + astParser->parse($code); + $this->traverser->traverse($ast); + + $this->assertEquals("Foo\\test", $ast[0]->stmts[0]->stmts[0]->exprs[0]->value); + } + + public function testParseNamespace() + { + $code = <<<'DOC' + astParser->parse($code); + $this->traverser->traverse($ast); + + $this->assertEquals("Foo", $ast[0]->stmts[0]->stmts[0]->exprs[0]->value); + } + + public function testParseTrait() + { + $code = <<<'DOC' + astParser->parse($code); + $this->traverser->traverse($ast); + + $this->assertEquals("Foo\Test", $ast[0]->stmts[0]->stmts[0]->stmts[0]->exprs[0]->value); + } + + public function testParseClass() + { + $code = <<<'DOC' + astParser->parse($code); + $this->traverser->traverse($ast); + + $this->assertEquals("Foo\Test", $ast[0]->stmts[0]->stmts[0]->stmts[0]->exprs[0]->value); + } + + public function testParseSelf() + { + $code = <<<'DOC' + astParser->parse($code); + $this->traverser->traverse($ast); + $this->assertEquals("Foo\Test", $ast[0]->stmts[0]->stmts[0]->stmts[0]->exprs[0]->class->name); + } + + + public function testParseSelfOutsideClass() + { + $code = <<<'DOC' + astParser->parse($code); + $this->traverser->traverse($ast); + $this->assertEquals("self", $ast[0]->stmts[0]->stmts[0]->exprs[0]->class->name); + } + + + public function testParseParent() + { + $code = <<<'DOC' + astParser->parse($code); + $this->traverser->traverse($ast); + $this->assertEquals("Foo\Bar", $ast[0]->stmts[0]->stmts[0]->stmts[0]->exprs[0]->class->name); + } +} diff --git a/test/PHPCfg/NameResolverTest.php b/test/unit/AstVisitor/NameResolverTest.php similarity index 58% rename from test/PHPCfg/NameResolverTest.php rename to test/unit/AstVisitor/NameResolverTest.php index 6c338ce..eee77d2 100644 --- a/test/PHPCfg/NameResolverTest.php +++ b/test/unit/AstVisitor/NameResolverTest.php @@ -9,23 +9,26 @@ * @license MIT See LICENSE at the root of the project for more info */ -namespace PHPCfg; +namespace PHPCfg\AstVisitor; -use PHPCfg\AstVisitor\NameResolver; use PhpParser\NodeTraverser; use PhpParser\Parser; use PhpParser\ParserFactory; -use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; +#[CoversClass(NameResolver::class)] class NameResolverTest extends TestCase { - /** @var Parser */ - private $astParser; + private Parser $astParser; + private NodeTraverser $traverser; protected function setUp(): void { $this->astParser = (new ParserFactory())->createForNewestSupportedVersion(); + $this->traverser = new NodeTraverser(); + $this->traverser->addVisitor(new NameResolver()); } #[DataProvider('provideIgnoresInvalidParamTypeInDocCommentCases')] @@ -42,9 +45,7 @@ public function testIgnoresInvalidParamTypeInDocComment($type) function foo(\$a) {} EOF; $ast = $this->astParser->parse($code); - $traverser = new NodeTraverser(); - $traverser->addVisitor(new NameResolver()); - $traverser->traverse($ast); + $this->traverser->traverse($ast); $this->assertEquals($doccomment, $ast[0]->getDocComment()->getText()); } @@ -83,9 +84,7 @@ function baz(Bar \$bar) {} EOF; $ast = $this->astParser->parse($code); - $traverser = new NodeTraverser(); - $traverser->addVisitor(new NameResolver()); - $traverser->traverse($ast); + $this->traverser->traverse($ast); $actual = $ast[1]->stmts[1]->getDocComment()->getText(); $this->assertEquals($expected, $actual); } @@ -114,10 +113,66 @@ function baz(Quux \$bar) {} EOF; $ast = $this->astParser->parse($code); - $traverser = new NodeTraverser(); - $traverser->addVisitor(new NameResolver()); - $traverser->traverse($ast); + $this->traverser->traverse($ast); $actual = $ast[1]->stmts[1]->getDocComment()->getText(); $this->assertEquals($expected, $actual); } + + public function testAnonymousClass() + { + $code = <<< EOF + astParser->parse($code); + $this->traverser->traverse($ast); + $actual = $ast[0]->stmts[0]->expr->expr->class->name; + $this->assertEquals("{anonymousClass}#1", $actual); + } + + public static function provideTypeDeclTests() + { + return [ + ['int', 'int'], + ['int|float', 'int|float'], + ['int&float', 'int&float'], + ['?bool', '?bool'], + ['int[]', 'int[]'], + ['$var', '$var'], + ['\\Exception', 'Exception'], + ['10', '10'], + + ]; + } + + #[DataProvider('provideTypeDeclTests')] + public function testTypeDeclTests(string $original, string $expected) + { + $formatString = <<< EOF + /** + * @param %s \$bar + */ + EOF; + $original = sprintf($formatString, $original); + $expected = sprintf($formatString, $expected); + $code = <<< EOF + astParser->parse($code); + $this->traverser->traverse($ast); + $actual = $ast[1]->stmts[0]->getDocComment()->getText(); + $this->assertEquals($expected, $actual); + } } diff --git a/test/unit/OpTest.php b/test/unit/OpTest.php new file mode 100644 index 0000000..9922845 --- /dev/null +++ b/test/unit/OpTest.php @@ -0,0 +1,62 @@ + 2, + 'filename' => 'foo', + + ]) extends Op {}; + $this->assertEquals(2, $op->getLine()); + $this->assertEquals('foo', $op->getFile()); + $this->assertEquals('default', $op->getAttribute('non-existant', 'default')); + $val = 'bar'; + $op->setAttribute('non-existant', $val); + $this->assertEquals('bar', $op->getAttribute('non-existant', 'default')); + + $this->assertStringMatchesFormat('anonymous%s', $op->getType()); + + $this->assertEquals([ + 'startLine' => 2, + 'filename' => 'foo', + 'non-existant' => 'bar', + ], $op->getAttributes()); + } + + public function testWriteArray() + { + $op = new class ([ + 'startLine' => 2, + 'filename' => 'foo', + + ]) extends Op { + public function addWriteRef(Operand $op): Operand + { + return parent::addWriteRef($op); + } + + }; + $mock = $this->createMock(Operand::class); + $mock->expects($this->once()) + ->method('addWriteOp') + ->with($this->identicalTo($op)); + $op->addWriteRef($mock); + } +}