From ac9f3386e5fc8e3f550296f6cf63d8c76f223790 Mon Sep 17 00:00:00 2001 From: Anthony Ferrara Date: Wed, 27 Aug 2025 16:55:50 -0400 Subject: [PATCH 01/14] Some type cleanups, changing method signatures to support more refined types. Breaking change --- .php-cs-fixer.php | 6 ++ demo.php | 8 +- lib/PHPCfg/AbstractVisitor.php | 24 +++--- lib/PHPCfg/Assertion.php | 21 +++-- lib/PHPCfg/Assertion/NegatedAssertion.php | 2 +- lib/PHPCfg/Assertion/TypeAssertion.php | 2 +- lib/PHPCfg/AstVisitor/LoopResolver.php | 5 +- lib/PHPCfg/AstVisitor/NameResolver.php | 10 +-- lib/PHPCfg/Block.php | 14 ++-- lib/PHPCfg/CatchTarget.php | 2 +- lib/PHPCfg/Func.php | 20 ++--- lib/PHPCfg/FuncContext.php | 26 +++--- lib/PHPCfg/Op/Expr/Param.php | 2 +- lib/PHPCfg/Op/Stmt/ClassLike.php | 4 +- lib/PHPCfg/Op/Stmt/Function_.php | 2 +- lib/PHPCfg/Op/Stmt/Property.php | 4 +- lib/PHPCfg/Op/Stmt/Try_.php | 2 +- lib/PHPCfg/Op/TraitUseAdaptation/Alias.php | 4 +- .../Op/TraitUseAdaptation/Precedence.php | 2 +- lib/PHPCfg/Op/Type.php | 1 - lib/PHPCfg/Operand/BoundVariable.php | 4 +- lib/PHPCfg/Parser.php | 80 ++++++++++--------- lib/PHPCfg/Printer.php | 51 ++++++------ lib/PHPCfg/Printer/GraphViz.php | 33 ++++---- lib/PHPCfg/Printer/Text.php | 6 +- lib/PHPCfg/Script.php | 4 +- lib/PHPCfg/Traverser.php | 26 +++--- lib/PHPCfg/Visitor.php | 18 ++--- lib/PHPCfg/Visitor/CallFinder.php | 6 +- lib/PHPCfg/Visitor/DebugVisitor.php | 6 +- lib/PHPCfg/Visitor/DeclarationFinder.php | 26 +++--- lib/PHPCfg/Visitor/Simplifier.php | 38 +++++---- lib/PHPCfg/Visitor/VariableFinder.php | 11 +-- test/PHPCfg/AttributesTest.php | 5 +- test/PHPCfg/MagicStringResolverTest.php | 2 +- test/PHPCfg/NameResolverTest.php | 2 +- test/PHPCfg/ParserTest.php | 13 +-- 37 files changed, 253 insertions(+), 239 deletions(-) diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index 25b5ade..3e17f30 100755 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -23,5 +23,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/demo.php b/demo.php index c9fe009..4aae789 100755 --- a/demo.php +++ b/demo.php @@ -13,7 +13,7 @@ require __DIR__ . '/vendor/autoload.php'; -$graphviz = false; +$graphviz = true; [$fileName, $code] = getCode($argc, $argv); $parser = new PHPCfg\Parser((new ParserFactory())->createForNewestSupportedVersion()); @@ -52,9 +52,11 @@ function getCode($argc, $argv) return [__FILE__, <<<'EOF' mode = $mode; } elseif (! $value instanceof Operand) { - throw new \RuntimeException('Invalid value supplied for Assertion: '); + throw new RuntimeException('Invalid value supplied for Assertion: '); } else { $this->mode = self::MODE_NONE; } $this->value = $value; } - public function getKind() + public function getKind(): string { return ''; } 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..289fe21 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_; @@ -74,14 +75,14 @@ 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); return new Goto_($loc[0], $node->getAttributes()); } - throw new \LogicException('Unimplemented Node Value Type'); + throw new LogicException('Unimplemented Node Value Type'); } protected function makeLabel() diff --git a/lib/PHPCfg/AstVisitor/NameResolver.php b/lib/PHPCfg/AstVisitor/NameResolver.php index 6c15f59..67bc854 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(); @@ -103,7 +103,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/Expr/Param.php b/lib/PHPCfg/Op/Expr/Param.php index 7967bb0..f80b936 100755 --- a/lib/PHPCfg/Op/Expr/Param.php +++ b/lib/PHPCfg/Op/Expr/Param.php @@ -13,8 +13,8 @@ use PHPCfg\Block; use PHPCfg\Op; -use PHPCfg\Op\Attributes\Attributable; use PHPCfg\Op\AttributableOp; +use PHPCfg\Op\Attributes\Attributable; use PHPCfg\Op\Expr; use PhpCfg\Operand; 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/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/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/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/Parser.php b/lib/PHPCfg/Parser.php index 392c2f9..254cee6 100755 --- a/lib/PHPCfg/Parser.php +++ b/lib/PHPCfg/Parser.php @@ -11,6 +11,7 @@ namespace PHPCfg; +use LogicException; use PHPCfg\Op\Stmt\Jump; use PHPCfg\Op\Stmt\JumpIf; use PHPCfg\Op\Stmt\TraitUse; @@ -28,6 +29,7 @@ use PhpParser\Node\Stmt; use PhpParser\NodeTraverser as AstTraverser; use PhpParser\Parser as AstParser; +use RuntimeException; class Parser { @@ -165,7 +167,7 @@ protected function parseFunc(Func $func, array $params, array $stmts) protected function parseNode(Node $node) { - if ($node instanceof Node\Expr) { + if ($node instanceof Expr) { $this->parseExprNode($node); return; @@ -178,7 +180,7 @@ protected function parseNode(Node $node) return; } - throw new \RuntimeException('Unknown Node Encountered : ' . $type); + throw new RuntimeException('Unknown Node Encountered : ' . $type); } protected function parseTypeList(array $types): array @@ -225,7 +227,7 @@ protected function parseTypeNode(?Node $node): Op\Type $this->mapAttributes($node), ); } - throw new \LogicException("Unknown type node: " . $node->getType()); + throw new LogicException("Unknown type node: " . $node->getType()); } protected function parseStmt_Block(Stmt\Block $node) @@ -259,7 +261,7 @@ protected function parseStmt_Class(Stmt\Class_ $node) protected function parseStmt_ClassConst(Stmt\ClassConst $node) { if (! $this->currentClass instanceof Op\Type\Literal) { - throw new \RuntimeException('Unknown current class'); + throw new RuntimeException('Unknown current class'); } foreach ($node->consts as $const) { $tmp = $this->block; @@ -279,7 +281,7 @@ protected function parseStmt_ClassConst(Stmt\ClassConst $node) protected function parseStmt_ClassMethod(Stmt\ClassMethod $node) { if (! $this->currentClass instanceof Op\Type\Literal) { - throw new \RuntimeException('Unknown current class'); + throw new RuntimeException('Unknown current class'); } $this->script->functions[] = $func = new Func( @@ -479,7 +481,7 @@ protected function parseStmt_GroupUse(Stmt\GroupUse $node) protected function parseStmt_HaltCompiler(Stmt\HaltCompiler $node) { $this->block->children[] = new Op\Terminal\Echo_( - $this->readVariable(new Operand\Literal($node->remaining)), + $this->readVariable(new Literal($node->remaining)), $this->mapAttributes($node), ); } @@ -511,7 +513,7 @@ protected function parseIf($node, Block $endBlock) $this->block = $elseBlock; - if ($node instanceof Node\Stmt\If_) { + if ($node instanceof Stmt\If_) { foreach ($node->elseifs as $elseIf) { $this->parseIf($elseIf, $endBlock); } @@ -546,7 +548,7 @@ protected function parseStmt_Interface(Stmt\Interface_ $node) protected function parseStmt_Label(Stmt\Label $node) { if (isset($this->ctx->labels[$node->name->toString()])) { - throw new \RuntimeException("Label '{$node->name->toString()}' already defined"); + throw new RuntimeException("Label '{$node->name->toString()}' already defined"); } $labelBlock = new Block($this->block); @@ -558,7 +560,7 @@ protected function parseStmt_Label(Stmt\Label $node) * @var array $attributes */ foreach ($this->ctx->unresolvedGotos[$node->name->toString()] as [$block, $attributes]) { - $block->children[] = new Op\Stmt\Jump($labelBlock, $attributes); + $block->children[] = new Jump($labelBlock, $attributes); $labelBlock->addParent($block); } unset($this->ctx->unresolvedGotos[$node->name->toString()]); @@ -614,7 +616,7 @@ protected function parseStmt_Return(Stmt\Return_ $node) if ($node->expr) { $expr = $this->readVariable($this->parseExprNode($node->expr)); } - $this->block->children[] = new Op\Terminal\Return_($expr, $this->mapAttributes($node)); + $this->block->children[] = new Return_($expr, $this->mapAttributes($node)); // Dump everything after the return $this->block = new Block($this->block); $this->block->dead = true; @@ -856,7 +858,7 @@ protected function parseExprNode($expr) if ($expr instanceof Node\Identifier) { return new Literal($expr->name); } - if ($expr instanceof Node\Expr\Variable) { + if ($expr instanceof Expr\Variable) { if (is_scalar($expr->name)) { if ($expr->name === 'this') { return new Operand\BoundVariable( @@ -893,7 +895,7 @@ protected function parseExprNode($expr) if ($expr instanceof Node\InterpolatedStringPart) { return new Literal($expr->value); } - if ($expr instanceof Node\Expr\AssignOp) { + if ($expr instanceof Expr\AssignOp) { $var = $this->parseExprNode($expr->var); $read = $this->readVariable($var); $write = $this->writeVariable($var); @@ -914,7 +916,7 @@ protected function parseExprNode($expr) 'Expr_AssignOp_ShiftRight' => Op\Expr\BinaryOp\ShiftRight::class, ][$expr->getType()]; if (empty($class)) { - throw new \RuntimeException('AssignOp Not Found: ' . $expr->getType()); + throw new RuntimeException('AssignOp Not Found: ' . $expr->getType()); } $attrs = $this->mapAttributes($expr); $this->block->children[] = $op = new $class($read, $e, $attrs); @@ -922,7 +924,7 @@ protected function parseExprNode($expr) return $op->result; } - if ($expr instanceof Node\Expr\BinaryOp) { + if ($expr instanceof AstBinaryOp) { if ($expr instanceof AstBinaryOp\LogicalAnd || $expr instanceof AstBinaryOp\BooleanAnd) { return $this->parseShortCircuiting($expr, false); } @@ -958,13 +960,13 @@ protected function parseExprNode($expr) 'Expr_BinaryOp_Spaceship' => Op\Expr\BinaryOp\Spaceship::class, ][$expr->getType()]; if (empty($class)) { - throw new \RuntimeException('BinaryOp Not Found: ' . $expr->getType()); + 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) { + if ($expr instanceof Expr\Cast) { $e = $this->readVariable($this->parseExprNode($expr->expr)); $class = [ 'Expr_Cast_Array' => Op\Expr\Cast\Array_::class, @@ -977,7 +979,7 @@ protected function parseExprNode($expr) ][$expr->getType()]; if (empty($class)) { - throw new \RuntimeException('Cast Not Found: ' . $expr->getType()); + throw new RuntimeException('Cast Not Found: ' . $expr->getType()); } $this->block->children[] = $op = new $class($e, $this->mapAttributes($expr)); @@ -996,10 +998,10 @@ protected function parseExprNode($expr) return $op; } } else { - throw new \RuntimeException('Unknown Expr Type ' . $expr->getType()); + throw new RuntimeException('Unknown Expr Type ' . $expr->getType()); } - throw new \RuntimeException('Invalid state, should never happen'); + throw new RuntimeException('Invalid state, should never happen'); } protected function parseArg(Node\Arg $expr) @@ -1254,7 +1256,7 @@ protected function parseExpr_FuncCall(Expr\FuncCall $expr) $op = new Op\Expr\FuncCall($name, $args, $this->mapAttributes($expr)); } - if ($name instanceof Operand\Literal) { + if ($name instanceof Literal) { static $assertionFunctions = [ 'is_array' => 'array', 'is_bool' => 'bool', @@ -1275,7 +1277,7 @@ protected function parseExpr_FuncCall(Expr\FuncCall $expr) if (isset($assertionFunctions[$lname])) { $op->result->addAssertion( $args[0], - new Assertion\TypeAssertion(new Operand\Literal($assertionFunctions[$lname])), + new Assertion\TypeAssertion(new Literal($assertionFunctions[$lname])), ); } } @@ -1322,7 +1324,7 @@ protected function parseListAssignment($expr, Operand $rhs) } if ($item->key === null) { - $key = new Operand\Literal($i); + $key = new Literal($i); } else { $key = $this->readVariable($this->parseExprNode($item->key)); } @@ -1357,7 +1359,7 @@ protected function parseExpr_MethodCall(Expr\MethodCall $expr) protected function parseExpr_New(Expr\New_ $expr) { - if ($expr->class instanceof Node\Stmt\Class_) { + if ($expr->class instanceof Stmt\Class_) { $this->parseStmt_Class($expr->class); $classExpr = $expr->class->name; } else { @@ -1376,7 +1378,7 @@ 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[] = $op = new Op\Expr\BinaryOp\Minus($read, new Literal(1), $this->mapAttributes($expr)); $this->block->children[] = new Op\Expr\Assign($write, $op->result, $this->mapAttributes($expr)); return $read; @@ -1387,7 +1389,7 @@ 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[] = $op = new Op\Expr\BinaryOp\Plus($read, new Literal(1), $this->mapAttributes($expr)); $this->block->children[] = new Op\Expr\Assign($write, $op->result, $this->mapAttributes($expr)); return $read; @@ -1398,7 +1400,7 @@ 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[] = $op = new Op\Expr\BinaryOp\Minus($read, new Literal(1), $this->mapAttributes($expr)); $this->block->children[] = new Op\Expr\Assign($write, $op->result, $this->mapAttributes($expr)); return $op->result; @@ -1409,7 +1411,7 @@ 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[] = $op = new Op\Expr\BinaryOp\Plus($read, new Literal(1), $this->mapAttributes($expr)); $this->block->children[] = new Op\Expr\Assign($write, $op->result, $this->mapAttributes($expr)); return $op->result; @@ -1528,7 +1530,7 @@ protected function parseExpr_ShellExec(Expr\ShellExec $expr) ); return new Op\Expr\FuncCall( - new Operand\Literal('shell_exec'), + new Literal('shell_exec'), [$arg->result], $this->mapAttributes($expr), ); @@ -1570,7 +1572,7 @@ protected function readAssertion(Assertion $assert) protected function throwUndefinedLabelError() { foreach ($this->ctx->unresolvedGotos as $name => $_) { - throw new \RuntimeException("'goto' to undefined label '{$name}'"); + throw new RuntimeException("'goto' to undefined label '{$name}'"); } } @@ -1664,7 +1666,7 @@ private function parseScalarNode(Node\Scalar $scalar) return new Literal('__FUNCTION__'); default: var_dump($scalar); - throw new \RuntimeException('Unknown how to deal with scalar type ' . $scalar->getType()); + throw new RuntimeException('Unknown how to deal with scalar type ' . $scalar->getType()); } } @@ -1694,7 +1696,7 @@ private function parseParameterList(Func $func, array $params) $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; } @@ -1755,14 +1757,14 @@ private function readVariable(Operand $var) // 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); } @@ -1771,13 +1773,13 @@ private function readVariable(Operand $var) private function writeVariable(Operand $var) { - 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 @@ -1808,7 +1810,7 @@ private function readVariableRecursive($name, Block $block) // 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 +1825,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 +1833,7 @@ private function readVariableRecursive($name, Block $block) return $var; } - private function getVariableName(Operand\Variable $var) + private function getVariableName(Variable $var) { assert($var->name instanceof Literal); diff --git a/lib/PHPCfg/Printer.php b/lib/PHPCfg/Printer.php index 57ed6be..6be9bbb 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 = []; @@ -335,7 +334,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 +349,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..095a302 100644 --- a/lib/PHPCfg/Script.php +++ b/lib/PHPCfg/Script.php @@ -14,8 +14,8 @@ class Script { /** @var Func[] */ - public $functions = []; + public array $functions = []; /** @var Func */ - public $main; + public Func $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/test/PHPCfg/AttributesTest.php b/test/PHPCfg/AttributesTest.php index 05ac937..b5a6bee 100755 --- a/test/PHPCfg/AttributesTest.php +++ b/test/PHPCfg/AttributesTest.php @@ -13,6 +13,7 @@ use PhpParser\ParserFactory; use PHPUnit\Framework\TestCase; +use RuntimeException; class AttributesTest extends TestCase { @@ -49,7 +50,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 +164,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/PHPCfg/MagicStringResolverTest.php b/test/PHPCfg/MagicStringResolverTest.php index acb3ece..f2ec819 100644 --- a/test/PHPCfg/MagicStringResolverTest.php +++ b/test/PHPCfg/MagicStringResolverTest.php @@ -11,8 +11,8 @@ namespace PHPCfg; -use PHPCfg\AstVisitor\NameResolver; use PHPCfg\AstVisitor\MagicStringResolver; +use PHPCfg\AstVisitor\NameResolver; use PhpParser\NodeTraverser; use PhpParser\Parser; use PhpParser\ParserFactory; diff --git a/test/PHPCfg/NameResolverTest.php b/test/PHPCfg/NameResolverTest.php index 6c338ce..ae662df 100644 --- a/test/PHPCfg/NameResolverTest.php +++ b/test/PHPCfg/NameResolverTest.php @@ -15,8 +15,8 @@ use PhpParser\NodeTraverser; use PhpParser\Parser; use PhpParser\ParserFactory; -use PHPUnit\Framework\TestCase; use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; class NameResolverTest extends TestCase { diff --git a/test/PHPCfg/ParserTest.php b/test/PHPCfg/ParserTest.php index 2823c36..98fc6de 100755 --- a/test/PHPCfg/ParserTest.php +++ b/test/PHPCfg/ParserTest.php @@ -13,8 +13,11 @@ use PhpParser; use PhpParser\ParserFactory; -use PHPUnit\Framework\TestCase; use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use RuntimeException; class ParserTest extends TestCase { @@ -32,7 +35,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(); } @@ -45,9 +48,9 @@ public function testParseAndDump($code, $expectedDump) public static function provideTestParseAndDump() { $dir = __DIR__ . '/../code'; - $iter = new \RecursiveIteratorIterator( - new \RecursiveDirectoryIterator($dir), - \RecursiveIteratorIterator::LEAVES_ONLY, + $iter = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($dir), + RecursiveIteratorIterator::LEAVES_ONLY, ); foreach ($iter as $file) { From 3995556e19b3bf9e43065858ec510edc46a0ab1b Mon Sep 17 00:00:00 2001 From: Anthony Ferrara Date: Fri, 29 Aug 2025 09:59:26 -0400 Subject: [PATCH 02/14] Add some tests, move to unit test more aggressively --- Makefile | 13 ++- .../AstVisitor/MagicStringResolverTest.php | 102 ++++++++++++++++++ lib/PHPCfg/AstVisitor/NameResolver.php | 1 - .../PHPCfg/AstVisitor}/NameResolverTest.php | 81 +++++++++++--- phpunit.xml.dist | 10 +- test/{PHPCfg/ParserTest.php => CodeTest.php} | 4 +- test/PHPCfg/MagicStringResolverTest.php | 63 ----------- ...butesTest.php => ParserAttributesTest.php} | 2 +- 8 files changed, 193 insertions(+), 83 deletions(-) create mode 100644 lib/PHPCfg/AstVisitor/MagicStringResolverTest.php rename {test/PHPCfg => lib/PHPCfg/AstVisitor}/NameResolverTest.php (58%) rename test/{PHPCfg/ParserTest.php => CodeTest.php} (96%) delete mode 100644 test/PHPCfg/MagicStringResolverTest.php rename test/{PHPCfg/AttributesTest.php => ParserAttributesTest.php} (99%) diff --git a/Makefile b/Makefile index a10bf0e..cf0e93a 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-text --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/lib/PHPCfg/AstVisitor/MagicStringResolverTest.php b/lib/PHPCfg/AstVisitor/MagicStringResolverTest.php new file mode 100644 index 0000000..d2204ed --- /dev/null +++ b/lib/PHPCfg/AstVisitor/MagicStringResolverTest.php @@ -0,0 +1,102 @@ +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 testParsesFunction() + { + $code = <<<'DOC' + astParser->parse($code); + $this->traverser->traverse($ast); + + $this->assertEquals("Foo\\test", $ast[0]->stmts[0]->stmts[0]->exprs[0]->value); + } +} + diff --git a/lib/PHPCfg/AstVisitor/NameResolver.php b/lib/PHPCfg/AstVisitor/NameResolver.php index 67bc854..cb75198 100644 --- a/lib/PHPCfg/AstVisitor/NameResolver.php +++ b/lib/PHPCfg/AstVisitor/NameResolver.php @@ -64,7 +64,6 @@ public function enterNode(Node $node) $regex, function ($match) { $type = $this->parseTypeDecl($match[2]); - return "@{$match[1]} {$type}"; }, $comment->getText(), diff --git a/test/PHPCfg/NameResolverTest.php b/lib/PHPCfg/AstVisitor/NameResolverTest.php similarity index 58% rename from test/PHPCfg/NameResolverTest.php rename to lib/PHPCfg/AstVisitor/NameResolverTest.php index ae662df..b2ee83f 100644 --- a/test/PHPCfg/NameResolverTest.php +++ b/lib/PHPCfg/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\Attributes\DataProvider; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Attributes\CoversClass; +#[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/phpunit.xml.dist b/phpunit.xml.dist index cbd6d51..35e96b4 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -5,13 +5,19 @@ stopOnFailure="false" bootstrap="./vendor/autoload.php"> - + ./test/ + + ./lib/ + - ./lib/PHPCfg/ + ./lib/ + + ./lib/ + \ No newline at end of file diff --git a/test/PHPCfg/ParserTest.php b/test/CodeTest.php similarity index 96% rename from test/PHPCfg/ParserTest.php rename to test/CodeTest.php index 98fc6de..f3cf957 100755 --- a/test/PHPCfg/ParserTest.php +++ b/test/CodeTest.php @@ -19,7 +19,7 @@ use RecursiveIteratorIterator; use RuntimeException; -class ParserTest extends TestCase +class CodeTest extends TestCase { #[DataProvider('provideTestParseAndDump')] public function testParseAndDump($code, $expectedDump) @@ -47,7 +47,7 @@ public function testParseAndDump($code, $expectedDump) public static function provideTestParseAndDump() { - $dir = __DIR__ . '/../code'; + $dir = __DIR__ . '/code'; $iter = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($dir), RecursiveIteratorIterator::LEAVES_ONLY, diff --git a/test/PHPCfg/MagicStringResolverTest.php b/test/PHPCfg/MagicStringResolverTest.php deleted file mode 100644 index f2ec819..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/PHPCfg/AttributesTest.php b/test/ParserAttributesTest.php similarity index 99% rename from test/PHPCfg/AttributesTest.php rename to test/ParserAttributesTest.php index b5a6bee..31ea9ce 100755 --- a/test/PHPCfg/AttributesTest.php +++ b/test/ParserAttributesTest.php @@ -15,7 +15,7 @@ use PHPUnit\Framework\TestCase; use RuntimeException; -class AttributesTest extends TestCase +class ParserAttributesTest extends TestCase { public function testDefault() { From 432c1cc9590f1740251ebb0fdfc6accf3de1f60b Mon Sep 17 00:00:00 2001 From: Anthony Ferrara Date: Fri, 29 Aug 2025 15:47:35 -0400 Subject: [PATCH 03/14] Fix a bunch of tests in AstVisitor --- .gitignore | 1 + Makefile | 2 +- lib/PHPCfg/AstVisitor/LoopResolver.php | 17 +-- lib/PHPCfg/AstVisitor/LoopResolverTest.php | 129 ++++++++++++++++ lib/PHPCfg/AstVisitor/MagicStringResolver.php | 32 ++-- .../AstVisitor/MagicStringResolverTest.php | 144 ++++++++++++++++++ 6 files changed, 295 insertions(+), 30 deletions(-) create mode 100644 lib/PHPCfg/AstVisitor/LoopResolverTest.php 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/Makefile b/Makefile index cf0e93a..7c56b4e 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ cs-fix: .PHONY: test test: - XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-text --display-deprecations + XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html ./coverage --display-deprecations .PHONY: t t: diff --git a/lib/PHPCfg/AstVisitor/LoopResolver.php b/lib/PHPCfg/AstVisitor/LoopResolver.php index 289fe21..cfd634c 100755 --- a/lib/PHPCfg/AstVisitor/LoopResolver.php +++ b/lib/PHPCfg/AstVisitor/LoopResolver.php @@ -20,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,8 +36,7 @@ public function enterNode(Node $node) case 'Stmt_Switch': $lbl = $this->makeLabel(); $this->breakStack[] = $lbl; - $this->continueStack[] = $lbl; - + $this->continueStack[] = $lbl; break; case 'Stmt_Do': case 'Stmt_While': @@ -58,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))]; } } @@ -77,8 +73,7 @@ protected function resolveStack(Node $node, array $stack) if ($num >= count($stack)) { 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()); } @@ -87,6 +82,6 @@ protected function resolveStack(Node $node, array $stack) 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/LoopResolverTest.php b/lib/PHPCfg/AstVisitor/LoopResolverTest.php new file mode 100644 index 0000000..3eaa33d --- /dev/null +++ b/lib/PHPCfg/AstVisitor/LoopResolverTest.php @@ -0,0 +1,129 @@ +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' + 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); + } +} \ No newline at end of file diff --git a/lib/PHPCfg/AstVisitor/MagicStringResolver.php b/lib/PHPCfg/AstVisitor/MagicStringResolver.php index 87da11b..95fc7d1 100644 --- a/lib/PHPCfg/AstVisitor/MagicStringResolver.php +++ b/lib/PHPCfg/AstVisitor/MagicStringResolver.php @@ -17,13 +17,16 @@ class MagicStringResolver extends NodeVisitorAbstract { - protected $classStack = []; - protected $parentStack = []; + protected array $namespaceStack = []; - protected $functionStack = []; + protected array $classStack = []; - protected $methodStack = []; + protected array $parentStack = []; + + protected array $functionStack = []; + + protected array $methodStack = []; public function enterNode(Node $node) { @@ -36,9 +39,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 +68,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 +94,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/MagicStringResolverTest.php b/lib/PHPCfg/AstVisitor/MagicStringResolverTest.php index d2204ed..c0223bf 100644 --- a/lib/PHPCfg/AstVisitor/MagicStringResolverTest.php +++ b/lib/PHPCfg/AstVisitor/MagicStringResolverTest.php @@ -80,6 +80,29 @@ public function test($foo) { $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' @@ -98,5 +121,126 @@ function test($foo) { $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); + } } From 81cda114fdee8b6d01972fed29a83aee10d53769 Mon Sep 17 00:00:00 2001 From: Anthony Ferrara Date: Fri, 29 Aug 2025 18:48:38 -0400 Subject: [PATCH 04/14] Some refactoring in progress --- lib/PHPCfg/Assertion.php | 19 +- lib/PHPCfg/AssertionTest.php | 70 +++ lib/PHPCfg/AstVisitor/LoopResolver.php | 2 +- lib/PHPCfg/AstVisitor/LoopResolverTest.php | 85 ++-- lib/PHPCfg/AstVisitor/MagicStringResolver.php | 1 - .../AstVisitor/MagicStringResolverTest.php | 11 +- lib/PHPCfg/AstVisitor/NameResolverTest.php | 6 +- lib/PHPCfg/Op.php | 24 +- lib/PHPCfg/OpTest.php | 62 +++ lib/PHPCfg/Parser.php | 415 ++++-------------- lib/PHPCfg/ParserHandler.php | 84 ++++ .../ParserHandler/Expr/ArrayDimFetch.php | 32 ++ lib/PHPCfg/ParserHandler/Expr/Array_.php | 40 ++ lib/PHPCfg/ParserHandler/Expr/Assign.php | 66 +++ lib/PHPCfg/ParserHandler/Expr/AssignRef.php | 28 ++ lib/PHPCfg/ParserHandler/Expr/Clone_.php | 29 ++ lib/PHPCfg/ParserHandler/Expr/Yield_.php | 34 ++ lib/PHPCfg/ParserHandler/Stmt/Block.php | 21 + lib/PHPCfg/ParserHandler/Stmt/Class_.php | 35 ++ lib/PHPCfg/ParserHandler/Stmt/Const_.php | 34 ++ lib/PHPCfg/ParserHandler/Stmt/Do_.php | 35 ++ lib/PHPCfg/ParserHandler/Stmt/Echo_.php | 27 ++ lib/PHPCfg/ParserHandler/Stmt/Expression.php | 21 + lib/PHPCfg/ParserHandler/Stmt/Foreach_.php | 63 +++ lib/PHPCfg/ParserHandler/Stmt/If_.php | 54 +++ test/CodeTest.php | 2 + test/ParserAttributesTest.php | 2 + 27 files changed, 913 insertions(+), 389 deletions(-) create mode 100644 lib/PHPCfg/AssertionTest.php create mode 100644 lib/PHPCfg/OpTest.php create mode 100644 lib/PHPCfg/ParserHandler.php create mode 100644 lib/PHPCfg/ParserHandler/Expr/ArrayDimFetch.php create mode 100644 lib/PHPCfg/ParserHandler/Expr/Array_.php create mode 100644 lib/PHPCfg/ParserHandler/Expr/Assign.php create mode 100644 lib/PHPCfg/ParserHandler/Expr/AssignRef.php create mode 100644 lib/PHPCfg/ParserHandler/Expr/Clone_.php create mode 100644 lib/PHPCfg/ParserHandler/Expr/Yield_.php create mode 100644 lib/PHPCfg/ParserHandler/Stmt/Block.php create mode 100644 lib/PHPCfg/ParserHandler/Stmt/Class_.php create mode 100644 lib/PHPCfg/ParserHandler/Stmt/Const_.php create mode 100644 lib/PHPCfg/ParserHandler/Stmt/Do_.php create mode 100644 lib/PHPCfg/ParserHandler/Stmt/Echo_.php create mode 100644 lib/PHPCfg/ParserHandler/Stmt/Expression.php create mode 100644 lib/PHPCfg/ParserHandler/Stmt/Foreach_.php create mode 100644 lib/PHPCfg/ParserHandler/Stmt/If_.php diff --git a/lib/PHPCfg/Assertion.php b/lib/PHPCfg/Assertion.php index cd18036..45bf8fe 100644 --- a/lib/PHPCfg/Assertion.php +++ b/lib/PHPCfg/Assertion.php @@ -42,11 +42,9 @@ public function __construct(array|Operand $value, $mode = self::MODE_NONE) if ($mode !== self::MODE_UNION && $mode !== self::MODE_INTERSECTION) { throw new RuntimeException('Invalid mode supplied for Assertion'); } - $this->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; } @@ -55,4 +53,17 @@ 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/AssertionTest.php b/lib/PHPCfg/AssertionTest.php new file mode 100644 index 0000000..f06e89e --- /dev/null +++ b/lib/PHPCfg/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/lib/PHPCfg/AstVisitor/LoopResolver.php b/lib/PHPCfg/AstVisitor/LoopResolver.php index cfd634c..af7c5de 100755 --- a/lib/PHPCfg/AstVisitor/LoopResolver.php +++ b/lib/PHPCfg/AstVisitor/LoopResolver.php @@ -36,7 +36,7 @@ public function enterNode(Node $node) case 'Stmt_Switch': $lbl = $this->makeLabel(); $this->breakStack[] = $lbl; - $this->continueStack[] = $lbl; + $this->continueStack[] = $lbl; break; case 'Stmt_Do': case 'Stmt_While': diff --git a/lib/PHPCfg/AstVisitor/LoopResolverTest.php b/lib/PHPCfg/AstVisitor/LoopResolverTest.php index 3eaa33d..233ff9d 100644 --- a/lib/PHPCfg/AstVisitor/LoopResolverTest.php +++ b/lib/PHPCfg/AstVisitor/LoopResolverTest.php @@ -11,17 +11,15 @@ namespace PHPCfg\AstVisitor; -use PHPCfg\AstVisitor\MagicStringResolver; -use PHPCfg\AstVisitor\NameResolver; +use LogicException; use PhpParser\NodeTraverser; use PhpParser\Parser; use PhpParser\ParserFactory; use PhpParser\PrettyPrinter; use PhpParser\PrettyPrinter\Standard; -use PHPUnit\Framework\TestCase; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; - +use PHPUnit\Framework\TestCase; #[CoversClass(LoopResolver::class)] class LoopResolverTest extends TestCase @@ -33,10 +31,10 @@ class LoopResolverTest extends TestCase protected function setUp(): void { $this->astParser = (new ParserFactory())->createForNewestSupportedVersion(); - $this->traverser = new NodeTraverser; + $this->traverser = new NodeTraverser(); // Always requires name resolution first - $this->traverser->addVisitor(new LoopResolver); - $this->printer = new Standard; + $this->traverser->addVisitor(new LoopResolver()); + $this->printer = new Standard(); } public static function provideTestResolve(): array @@ -48,12 +46,27 @@ public static function provideTestResolve(): array } while (true); DOC, <<<'DOC' - do { - goto compiled_label_%s_1; + 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: - } while (true); - compiled_label_%s_1: - DOC], + DOC], [<<<'DOC' while(true) { continue; @@ -61,13 +74,13 @@ public static function provideTestResolve(): array } DOC, <<<'DOC' - while (true) { - goto compiled_label_%s_0; - goto compiled_label_%s_1; - compiled_label_%s_0: - } - compiled_label_%s_1: - 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) { @@ -79,19 +92,19 @@ public static function provideTestResolve(): array } 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: + 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_3: - compiled_label_%s_0: - } - compiled_label_%s_1: - DOC], + compiled_label_%s_1: + DOC], ]; } @@ -103,27 +116,29 @@ public function testResolve($code, $expect) $this->assertStringMatchesFormat($expect, $this->printer->prettyPrint($new)); } - public function testBreakTooLarge() { + public function testBreakTooLarge() + { $code = 'astParser->parse($code); - $this->expectException(\LogicException::class); + $this->expectException(LogicException::class); $this->expectExceptionMessage("Too high of a count for Stmt_Break"); $this->traverser->traverse($ast); } - public function testBreakTypeWrong() { + public function testBreakTypeWrong() + { $code = 'astParser->parse($code); - $this->expectException(\LogicException::class); + $this->expectException(LogicException::class); $this->expectExceptionMessage("Unimplemented Node Value Type"); $this->traverser->traverse($ast); } -} \ No newline at end of file +} diff --git a/lib/PHPCfg/AstVisitor/MagicStringResolver.php b/lib/PHPCfg/AstVisitor/MagicStringResolver.php index 95fc7d1..789916f 100644 --- a/lib/PHPCfg/AstVisitor/MagicStringResolver.php +++ b/lib/PHPCfg/AstVisitor/MagicStringResolver.php @@ -17,7 +17,6 @@ class MagicStringResolver extends NodeVisitorAbstract { - protected array $namespaceStack = []; protected array $classStack = []; diff --git a/lib/PHPCfg/AstVisitor/MagicStringResolverTest.php b/lib/PHPCfg/AstVisitor/MagicStringResolverTest.php index c0223bf..7d49b1b 100644 --- a/lib/PHPCfg/AstVisitor/MagicStringResolverTest.php +++ b/lib/PHPCfg/AstVisitor/MagicStringResolverTest.php @@ -11,13 +11,11 @@ namespace PHPCfg\AstVisitor; -use PHPCfg\AstVisitor\MagicStringResolver; -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\TestCase; #[CoversClass(MagicStringResolver::class)] class MagicStringResolverTest extends TestCase @@ -28,10 +26,10 @@ class MagicStringResolverTest extends TestCase protected function setUp(): void { $this->astParser = (new ParserFactory())->createForNewestSupportedVersion(); - $this->traverser = new NodeTraverser; + $this->traverser = new NodeTraverser(); // Always requires name resolution first - $this->traverser->addVisitor(new NameResolver); - $this->traverser->addVisitor(new MagicStringResolver); + $this->traverser->addVisitor(new NameResolver()); + $this->traverser->addVisitor(new MagicStringResolver()); } public function testParsesLineNumberCorrectly() @@ -243,4 +241,3 @@ public function test() { $this->assertEquals("Foo\Bar", $ast[0]->stmts[0]->stmts[0]->stmts[0]->exprs[0]->class->name); } } - diff --git a/lib/PHPCfg/AstVisitor/NameResolverTest.php b/lib/PHPCfg/AstVisitor/NameResolverTest.php index b2ee83f..eee77d2 100644 --- a/lib/PHPCfg/AstVisitor/NameResolverTest.php +++ b/lib/PHPCfg/AstVisitor/NameResolverTest.php @@ -14,9 +14,9 @@ use PhpParser\NodeTraverser; use PhpParser\Parser; use PhpParser\ParserFactory; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\Attributes\CoversClass; #[CoversClass(NameResolver::class)] class NameResolverTest extends TestCase @@ -27,8 +27,8 @@ class NameResolverTest extends TestCase protected function setUp(): void { $this->astParser = (new ParserFactory())->createForNewestSupportedVersion(); - $this->traverser = new NodeTraverser; - $this->traverser->addVisitor(new NameResolver); + $this->traverser = new NodeTraverser(); + $this->traverser->addVisitor(new NameResolver()); } #[DataProvider('provideIgnoresInvalidParamTypeInDocCommentCases')] 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/OpTest.php b/lib/PHPCfg/OpTest.php new file mode 100644 index 0000000..9922845 --- /dev/null +++ b/lib/PHPCfg/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); + } +} diff --git a/lib/PHPCfg/Parser.php b/lib/PHPCfg/Parser.php index 254cee6..03271e4 100755 --- a/lib/PHPCfg/Parser.php +++ b/lib/PHPCfg/Parser.php @@ -29,6 +29,8 @@ use PhpParser\Node\Stmt; use PhpParser\NodeTraverser as AstTraverser; use PhpParser\Parser as AstParser; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; use RuntimeException; class Parser @@ -40,25 +42,25 @@ 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 $handlers = []; public function __construct(AstParser $astParser, ?AstTraverser $astTraverser = null) { @@ -70,6 +72,28 @@ 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(); + } + + 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); + $obj = new $class($this); + $this->handlers[$obj->getName()] = $obj; + } } /** @@ -104,7 +128,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; @@ -117,7 +141,7 @@ public function parseNodes(array $nodes, Block $block) return $end; } - protected function parseFunc(Func $func, array $params, array $stmts) + protected function parseFunc(Func $func, array $params, array $stmts): void { // Switch to new function context $prevCtx = $this->ctx; @@ -165,7 +189,7 @@ protected function parseFunc(Func $func, array $params, array $stmts) $this->ctx = $prevCtx; } - protected function parseNode(Node $node) + protected function parseNode(Node $node): void { if ($node instanceof Expr) { $this->parseExprNode($node); @@ -174,7 +198,10 @@ protected function parseNode(Node $node) } $type = $node->getType(); - if (method_exists($this, 'parse' . $type)) { + if (isset($this->handlers[$type])) { + $this->handlers[$type]->handleStmt($node); + return; + } elseif (method_exists($this, 'parse' . $type)) { $this->{'parse' . $type}($node); return; @@ -183,7 +210,7 @@ protected function parseNode(Node $node) throw new RuntimeException('Unknown Node Encountered : ' . $type); } - protected function parseTypeList(array $types): array + public function parseTypeList(array $types): array { $parsedTypes = []; foreach ($types as $type) { @@ -193,7 +220,7 @@ protected function parseTypeList(array $types): array return $parsedTypes; } - protected function parseTypeNode(?Node $node): Op\Type + public function parseTypeNode(?Node $node): Op\Type { if (is_null($node)) { return new Op\Type\Mixed_(); @@ -230,35 +257,7 @@ protected function parseTypeNode(?Node $node): Op\Type 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) + protected function parseStmt_ClassConst(Stmt\ClassConst $node): void { if (! $this->currentClass instanceof Op\Type\Literal) { throw new RuntimeException('Unknown current class'); @@ -278,7 +277,7 @@ protected function parseStmt_ClassConst(Stmt\ClassConst $node) } } - protected function parseStmt_ClassMethod(Stmt\ClassMethod $node) + protected function parseStmt_ClassMethod(Stmt\ClassMethod $node): void { if (! $this->currentClass instanceof Op\Type\Literal) { throw new RuntimeException('Unknown current class'); @@ -315,57 +314,14 @@ protected function parseStmt_ClassMethod(Stmt\ClassMethod $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) + protected function parseStmt_Declare(Stmt\Declare_ $node): void { // 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) + protected function parseStmt_For(Stmt\For_ $node): void { $this->parseExprList($node->init, self::MODE_READ); @@ -392,50 +348,9 @@ protected function parseStmt_For(Stmt\For_ $node) $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) + protected function parseStmt_Function(Stmt\Function_ $node): void { $this->script->functions[] = $func = new Func( $node->namespacedName->toString(), @@ -448,7 +363,7 @@ protected function parseStmt_Function(Stmt\Function_ $node) $func->callableOp = $function; } - protected function parseStmt_Global(Stmt\Global_ $node) + protected function parseStmt_Global(Stmt\Global_ $node): void { foreach ($node->vars as $var) { // TODO $var is not necessarily a Variable node @@ -459,7 +374,7 @@ protected function parseStmt_Global(Stmt\Global_ $node) } } - protected function parseStmt_Goto(Stmt\Goto_ $node) + protected function parseStmt_Goto(Stmt\Goto_ $node): void { $attributes = $this->mapAttributes($node); if (isset($this->ctx->labels[$node->name->toString()])) { @@ -473,12 +388,12 @@ protected function parseStmt_Goto(Stmt\Goto_ $node) $this->block->dead = true; } - protected function parseStmt_GroupUse(Stmt\GroupUse $node) + protected function parseStmt_GroupUse(Stmt\GroupUse $node): void { // ignore use statements, since names are already resolved } - protected function parseStmt_HaltCompiler(Stmt\HaltCompiler $node) + protected function parseStmt_HaltCompiler(Stmt\HaltCompiler $node): void { $this->block->children[] = new Op\Terminal\Echo_( $this->readVariable(new Literal($node->remaining)), @@ -486,51 +401,12 @@ protected function parseStmt_HaltCompiler(Stmt\HaltCompiler $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 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) + protected function parseStmt_InlineHTML(Stmt\InlineHTML $node): void { $this->block->children[] = new Op\Terminal\Echo_($this->parseExprNode($node->value), $this->mapAttributes($node)); } - protected function parseStmt_Interface(Stmt\Interface_ $node) + protected function parseStmt_Interface(Stmt\Interface_ $node): void { $name = $this->parseTypeNode($node->namespacedName); $old = $this->currentClass; @@ -545,7 +421,7 @@ protected function parseStmt_Interface(Stmt\Interface_ $node) $this->currentClass = $old; } - protected function parseStmt_Label(Stmt\Label $node) + protected function parseStmt_Label(Stmt\Label $node): void { if (isset($this->ctx->labels[$node->name->toString()])) { throw new RuntimeException("Label '{$node->name->toString()}' already defined"); @@ -568,18 +444,18 @@ protected function parseStmt_Label(Stmt\Label $node) $this->block = $this->ctx->labels[$node->name->toString()] = $labelBlock; } - protected function parseStmt_Namespace(Stmt\Namespace_ $node) + protected function parseStmt_Namespace(Stmt\Namespace_ $node): void { $this->currentNamespace = $node->name; $this->block = $this->parseNodes($node->stmts, $this->block); } - protected function parseStmt_Nop(Stmt\Nop $node) + protected function parseStmt_Nop(Stmt\Nop $node): void { // Nothing to see here, move along } - protected function parseStmt_Property(Stmt\Property $node) + protected function parseStmt_Property(Stmt\Property $node): void { $visibility = $node->flags & Modifiers::VISIBILITY_MASK; $static = $node->flags & Modifiers::STATIC; @@ -610,7 +486,7 @@ protected function parseStmt_Property(Stmt\Property $node) } } - protected function parseStmt_Return(Stmt\Return_ $node) + protected function parseStmt_Return(Stmt\Return_ $node): void { $expr = null; if ($node->expr) { @@ -622,7 +498,7 @@ protected function parseStmt_Return(Stmt\Return_ $node) $this->block->dead = true; } - protected function parseStmt_Static(Stmt\Static_ $node) + protected function parseStmt_Static(Stmt\Static_ $node): void { foreach ($node->vars as $var) { $defaultBlock = null; @@ -642,7 +518,7 @@ protected function parseStmt_Static(Stmt\Static_ $node) } } - protected function parseStmt_Switch(Stmt\Switch_ $node) + protected function parseStmt_Switch(Stmt\Switch_ $node): void { if ($this->switchCanUseJumptable($node)) { $this->compileJumptableSwitch($node); @@ -842,7 +718,7 @@ protected function parseExprList(array $expr, $readWrite = self::MODE_NONE): arr return $vars; } - protected function parseExprNode($expr) + public function parseExprNode($expr) { if (null === $expr) { return; @@ -987,7 +863,10 @@ protected function parseExprNode($expr) } $method = 'parse' . $expr->getType(); - if (method_exists($this, $method)) { + + if (isset($this->handlers[$expr->getType()])) { + return $this->handlers[$expr->getType()]->handleExpr($expr); + } elseif (method_exists($this, $method)) { $op = $this->{$method}($expr); if ($op instanceof Op) { $this->block->children[] = $op; @@ -1009,77 +888,27 @@ protected function parseArg(Node\Arg $expr) return $this->readVariable($this->parseExprNode($expr->value)); } - protected function parseAttribute(Node\Attribute $attr) + public 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) + public 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) + public 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) { @@ -1160,11 +989,6 @@ protected function parseExpr_ClassConstFetch(Expr\ClassConstFetch $expr) 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()) { @@ -1312,41 +1136,6 @@ protected function parseExpr_Isset(Expr\Isset_ $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 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( @@ -1360,7 +1149,7 @@ protected function parseExpr_MethodCall(Expr\MethodCall $expr) protected function parseExpr_New(Expr\New_ $expr) { if ($expr->class instanceof Stmt\Class_) { - $this->parseStmt_Class($expr->class); + $this->parseNode($expr->class); $classExpr = $expr->class->name; } else { $classExpr = $expr->class; @@ -1384,7 +1173,7 @@ protected function parseExpr_PostDec(Expr\PostDec $expr) return $read; } - protected function parseExpr_PostInc(Expr\PostInc $expr) + protected function parseExpr_PostInc(Expr\PostInc $expr): Operand { $var = $this->parseExprNode($expr->var); $read = $this->readVariable($var); @@ -1395,7 +1184,7 @@ protected function parseExpr_PostInc(Expr\PostInc $expr) return $read; } - protected function parseExpr_PreDec(Expr\PreDec $expr) + protected function parseExpr_PreDec(Expr\PreDec $expr): Operand { $var = $this->parseExprNode($expr->var); $read = $this->readVariable($var); @@ -1406,7 +1195,7 @@ protected function parseExpr_PreDec(Expr\PreDec $expr) return $op->result; } - protected function parseExpr_PreInc(Expr\PreInc $expr) + protected function parseExpr_PreInc(Expr\PreInc $expr): Operand { $var = $this->parseExprNode($expr->var); $read = $this->readVariable($var); @@ -1417,12 +1206,12 @@ protected function parseExpr_PreInc(Expr\PreInc $expr) return $op->result; } - protected function parseExpr_Print(Expr\Print_ $expr) + protected function parseExpr_Print(Expr\Print_ $expr): Op { return new Op\Expr\Print_($this->readVariable($this->parseExprNode($expr->expr)), $this->mapAttributes($expr)); } - protected function parseExpr_PropertyFetch(Expr\PropertyFetch $expr) + protected function parseExpr_PropertyFetch(Expr\PropertyFetch $expr): Op { return new Op\Expr\PropertyFetch( $this->readVariable($this->parseExprNode($expr->var)), @@ -1431,7 +1220,7 @@ protected function parseExpr_PropertyFetch(Expr\PropertyFetch $expr) ); } - protected function parseExpr_StaticCall(Expr\StaticCall $expr) + protected function parseExpr_StaticCall(Expr\StaticCall $expr): Op { return new Op\Expr\StaticCall( $this->readVariable($this->parseExprNode($expr->class)), @@ -1441,7 +1230,7 @@ protected function parseExpr_StaticCall(Expr\StaticCall $expr) ); } - protected function parseExpr_StaticPropertyFetch(Expr\StaticPropertyFetch $expr) + protected function parseExpr_StaticPropertyFetch(Expr\StaticPropertyFetch $expr): Op { return new Op\Expr\StaticPropertyFetch( $this->readVariable($this->parseExprNode($expr->class)), @@ -1450,7 +1239,7 @@ protected function parseExpr_StaticPropertyFetch(Expr\StaticPropertyFetch $expr) ); } - protected function parseExpr_Ternary(Expr\Ternary $expr) + protected function parseExpr_Ternary(Expr\Ternary $expr): Operand { $attrs = $this->mapAttributes($expr); $cond = $this->readVariable($this->parseExprNode($expr->cond)); @@ -1498,31 +1287,17 @@ protected function parseExpr_Ternary(Expr\Ternary $expr) return $result; } - protected function parseExpr_UnaryMinus(Expr\UnaryMinus $expr) + protected function parseExpr_UnaryMinus(Expr\UnaryMinus $expr): Op { return new Op\Expr\UnaryMinus($this->readVariable($this->parseExprNode($expr->expr)), $this->mapAttributes($expr)); } - protected function parseExpr_UnaryPlus(Expr\UnaryPlus $expr) + protected function parseExpr_UnaryPlus(Expr\UnaryPlus $expr): Op { 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) + protected function parseExpr_ShellExec(Expr\ShellExec $expr): Op { $this->block->children[] = $arg = new Op\Expr\ConcatList( $this->parseExprList($expr->parts, self::MODE_READ), @@ -1536,7 +1311,7 @@ protected function parseExpr_ShellExec(Expr\ShellExec $expr) ); } - protected function processAssertions(Operand $op, Block $if, Block $else) + public function processAssertions(Operand $op, Block $if, Block $else): void { $block = $this->block; foreach ($op->assertions as $assert) { @@ -1556,7 +1331,7 @@ protected function processAssertions(Operand $op, Block $if, Block $else) $this->block = $block; } - protected function readAssertion(Assertion $assert) + protected function readAssertion(Assertion $assert): Assertion { if ($assert->value instanceof Operand) { return new $assert($this->readVariable($assert->value)); @@ -1569,14 +1344,14 @@ protected function readAssertion(Assertion $assert) return new $assert($vars, $assert->mode); } - protected function throwUndefinedLabelError() + protected function throwUndefinedLabelError(): void { foreach ($this->ctx->unresolvedGotos as $name => $_) { throw new RuntimeException("'goto' to undefined label '{$name}'"); } } - private function switchCanUseJumptable(Stmt\Switch_ $node) + private function switchCanUseJumptable(Stmt\Switch_ $node): bool { foreach ($node->cases as $case) { if ( @@ -1591,7 +1366,7 @@ private function switchCanUseJumptable(Stmt\Switch_ $node) return true; } - private function compileJumptableSwitch(Stmt\Switch_ $node) + private function compileJumptableSwitch(Stmt\Switch_ $node): void { $cond = $this->readVariable($this->parseExprNode($node->cond)); $cases = []; @@ -1632,7 +1407,7 @@ private function compileJumptableSwitch(Stmt\Switch_ $node) $this->block = $endBlock; } - private function parseScalarNode(Node\Scalar $scalar) + private function parseScalarNode(Node\Scalar $scalar): Operand { switch ($scalar->getType()) { case 'Scalar_InterpolatedString': @@ -1670,7 +1445,7 @@ private function parseScalarNode(Node\Scalar $scalar) } } - private function parseParameterList(Func $func, array $params) + private function parseParameterList(Func $func, array $params): array { if (empty($params)) { return []; @@ -1703,7 +1478,7 @@ private function parseParameterList(Func $func, array $params) return $result; } - private function parseShortCircuiting(AstBinaryOp $expr, $isOr) + private function parseShortCircuiting(AstBinaryOp $expr, $isOr): Operand { $result = new Temporary(); $longBlock = new Block(null, $this->block->catchTarget); @@ -1741,7 +1516,7 @@ private function parseShortCircuiting(AstBinaryOp $expr, $isOr) return $result; } - private function mapAttributes(Node $expr) + public function mapAttributes(Node $expr): array { return array_merge( [ @@ -1751,7 +1526,7 @@ 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 @@ -1771,7 +1546,7 @@ private function readVariable(Operand $var) return $var; } - private function writeVariable(Operand $var) + public function writeVariable(Operand $var): Operand { while ($var instanceof Temporary && $var->original) { $var = $var->original; @@ -1789,7 +1564,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]; @@ -1798,12 +1573,12 @@ 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) { @@ -1833,7 +1608,7 @@ private function readVariableRecursive($name, Block $block) return $var; } - private function getVariableName(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..cf66807 --- /dev/null +++ b/lib/PHPCfg/ParserHandler.php @@ -0,0 +1,84 @@ +parser = $parser; + } + + public function handleExpr(Node\Expr $expr): Operand + { + throw new \LogicException("Expr " . $expr->getType() . " not Implemented Yet"); + } + public function handleStmt(Node\Stmt $stmt): void + { + throw new \LogicException("Stmt " . $stmt->getType() . " not Implemented Yet"); + } + + 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 $expr): array + { + return array_merge( + [ + 'filename' => $this->parser->fileName, + ], + $expr->getAttributes(), + ); + } + + protected function addOp(Op $op): void + { + $this->parser->block->children[] = $op; + } + + protected function addExpr(Op\Expr $expr): Operand + { + $this->addOp($expr); + return $expr->result; + } + +} diff --git a/lib/PHPCfg/ParserHandler/Expr/ArrayDimFetch.php b/lib/PHPCfg/ParserHandler/Expr/ArrayDimFetch.php new file mode 100644 index 0000000..2b001f4 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/ArrayDimFetch.php @@ -0,0 +1,32 @@ +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))); + } + +} \ No newline at end of file diff --git a/lib/PHPCfg/ParserHandler/Expr/Array_.php b/lib/PHPCfg/ParserHandler/Expr/Array_.php new file mode 100644 index 0000000..4001b43 --- /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))); + } + +} \ No newline at end of file diff --git a/lib/PHPCfg/ParserHandler/Expr/Assign.php b/lib/PHPCfg/ParserHandler/Expr/Assign.php new file mode 100644 index 0000000..f539c48 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/Assign.php @@ -0,0 +1,66 @@ +parser->readVariable($this->parser->parseExprNode($expr->expr)); + if ($expr->var instanceof Expr\List_ || $expr->var instanceof Expr\Array_) { + self::parseListAssignment($expr->var, $e, $this->parser, $this->mapAttributes($expr->var)); + + return $e; + } + $v = $this->parser->writeVariable($this->parser->parseExprNode($expr->var)); + + return $this->addExpr(new Op\Expr\Assign($v, $e, $this->mapAttributes($expr))); + } + + /** + * @param Expr\List_|Expr\Array_ $expr + */ + public static function parseListAssignment(Expr $expr, Operand $rhs, Parser $parser, array $attributes): void + { + foreach ($expr->items as $i => $item) { + if (null === $item) { + continue; + } + + if ($item->key === null) { + $key = new Operand\Literal($i); + } else { + $key = $parser->readVariable($parser->parseExprNode($item->key)); + } + + $var = $item->value; + $fetch = new Op\Expr\ArrayDimFetch($rhs, $key, $attributes); + $parser->block->children[] = $fetch; + if ($var instanceof Expr\List_ || $var instanceof Expr\Array_) { + self::parseListAssignment($var, $fetch->result, $parser, $attributes); + + continue; + } + + $assign = new Op\Expr\Assign( + $parser->writeVariable($parser->parseExprNode($var)), + $fetch->result, + $attributes, + ); + $parser->block->children[] = $assign; + } + } +} diff --git a/lib/PHPCfg/ParserHandler/Expr/AssignRef.php b/lib/PHPCfg/ParserHandler/Expr/AssignRef.php new file mode 100644 index 0000000..ae0b087 --- /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))); + } + +} \ No newline at end of file diff --git a/lib/PHPCfg/ParserHandler/Expr/Clone_.php b/lib/PHPCfg/ParserHandler/Expr/Clone_.php new file mode 100644 index 0000000..ec718e9 --- /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) + )); + + } + +} \ No newline at end of file diff --git a/lib/PHPCfg/ParserHandler/Expr/Yield_.php b/lib/PHPCfg/ParserHandler/Expr/Yield_.php new file mode 100644 index 0000000..45ccf23 --- /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))); + } + +} \ No newline at end of file diff --git a/lib/PHPCfg/ParserHandler/Stmt/Block.php b/lib/PHPCfg/ParserHandler/Stmt/Block.php new file mode 100644 index 0000000..e41a84f --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Block.php @@ -0,0 +1,21 @@ +parser->parseNodes($node->stmts, $this->block()); + } +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/Class_.php b/lib/PHPCfg/ParserHandler/Stmt/Class_.php new file mode 100644 index 0000000..a3cf756 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Class_.php @@ -0,0 +1,35 @@ +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..ca81b0e --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Const_.php @@ -0,0 +1,34 @@ +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/Do_.php b/lib/PHPCfg/ParserHandler/Stmt/Do_.php new file mode 100644 index 0000000..0381e53 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Do_.php @@ -0,0 +1,35 @@ +createBlockWithParent(); + $loopEnd = $this->createBlockWithCatchTarget(); + $this->addOp(new Op\Stmt\Jump($loopBody, $this->mapAttributes($node))); + $loopBody->addParent($this->block()); + + $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); + $loopBody->addParent($this->block()); + $loopEnd->addParent($this->block()); + + $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..21bfa01 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Echo_.php @@ -0,0 +1,27 @@ +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..cb77870 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Expression.php @@ -0,0 +1,21 @@ +parser->parseExprNode($node->expr); + } +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/Foreach_.php b/lib/PHPCfg/ParserHandler/Stmt/Foreach_.php new file mode 100644 index 0000000..09a434a --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Foreach_.php @@ -0,0 +1,63 @@ +mapAttributes($node); + $iterable = $this->parser->readVariable($this->parser->parseExprNode($node->expr)); + $this->addOp(new Op\Iterator\Reset($iterable, $attrs)); + + $loopInit = $this->createBlockWithParent(); + $loopBody = $this->createBlockWithCatchTarget(); + $loopEnd = $this->createBlockWithCatchTarget(); + + $this->addOp(new Op\Stmt\Jump($loopInit, $attrs)); + + $loopInit->children[] = $validOp = new Op\Iterator\Valid($iterable, $attrs); + $loopInit->children[] = new Op\Stmt\JumpIf($validOp->result, $loopBody, $loopEnd, $attrs); + $this->parser->processAssertions($validOp->result, $loopBody, $loopEnd); + $loopBody->addParent($loopInit); + $loopEnd->addParent($loopInit); + + $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 Expr\List_ || $node->valueVar instanceof Expr\Array_) { + Assign::parseListAssignment($node->valueVar, $valueOp->result, $this->parser, $this->mapAttributes($node->valueVar)); + } 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)); + + $loopInit->addParent($this->block()); + + $this->block($loopEnd); + } + +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/If_.php b/lib/PHPCfg/ParserHandler/Stmt/If_.php new file mode 100644 index 0000000..97a4a6b --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/If_.php @@ -0,0 +1,54 @@ +createBlockWithCatchTarget(); + $this->parseIf($node, $endBlock); + $this->block($endBlock); + } + + protected function parseIf(Stmt $node, Block $endBlock): void + { + $attrs = $this->mapAttributes($node); + $cond = $this->parser->readVariable($this->parser->parseExprNode($node->cond)); + $ifBlock = $this->createBlockWithParent(); + $elseBlock = $this->createBlockWithParent(); + + $this->addOp(new Op\Stmt\JumpIf($cond, $ifBlock, $elseBlock, $attrs)); + $this->parser->processAssertions($cond, $ifBlock, $elseBlock); + + $this->block($this->parser->parseNodes($node->stmts, $ifBlock)); + + $this->addOp(new Op\Stmt\Jump($endBlock, $attrs)); + $endBlock->addParent($this->block()); + + $this->block($elseBlock); + + if ($node instanceof 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)); + $endBlock->addParent($this->block()); + } + } +} diff --git a/test/CodeTest.php b/test/CodeTest.php index f3cf957..aabf85d 100755 --- a/test/CodeTest.php +++ b/test/CodeTest.php @@ -13,12 +13,14 @@ use PhpParser; use PhpParser\ParserFactory; +use PHPUnit\Framework\Attributes\CoversNothing; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use RuntimeException; +#[CoversNothing] class CodeTest extends TestCase { #[DataProvider('provideTestParseAndDump')] diff --git a/test/ParserAttributesTest.php b/test/ParserAttributesTest.php index 31ea9ce..dc95c98 100755 --- a/test/ParserAttributesTest.php +++ b/test/ParserAttributesTest.php @@ -12,9 +12,11 @@ namespace PHPCfg; use PhpParser\ParserFactory; +use PHPUnit\Framework\Attributes\CoversNothing; use PHPUnit\Framework\TestCase; use RuntimeException; +#[CoversNothing] class ParserAttributesTest extends TestCase { public function testDefault() From b406142474d9adbe4e3bf5fac6add2d5755ec01b Mon Sep 17 00:00:00 2001 From: Anthony Ferrara Date: Fri, 29 Aug 2025 21:14:17 -0400 Subject: [PATCH 05/14] Refactor rest of Expressions --- lib/PHPCfg/Operand/Temporary.php | 2 +- lib/PHPCfg/Parser.php | 590 +----------------- lib/PHPCfg/ParserHandler.php | 14 +- lib/PHPCfg/ParserHandler/Batch/AssignOp.php | 58 ++ lib/PHPCfg/ParserHandler/Batch/BinaryOp.php | 118 ++++ lib/PHPCfg/ParserHandler/Batch/IncDec.php | 56 ++ lib/PHPCfg/ParserHandler/Batch/Unary.php | 64 ++ .../ParserHandler/Expr/ArrayDimFetch.php | 3 +- lib/PHPCfg/ParserHandler/Expr/Array_.php | 3 +- .../ParserHandler/Expr/ArrowFunction.php | 43 ++ lib/PHPCfg/ParserHandler/Expr/AssignRef.php | 3 +- .../ParserHandler/Expr/ClassConstFetch.php | 28 + lib/PHPCfg/ParserHandler/Expr/Clone_.php | 5 +- lib/PHPCfg/ParserHandler/Expr/Closure_.php | 48 ++ lib/PHPCfg/ParserHandler/Expr/ConstFetch.php | 45 ++ .../ParserHandler/Expr/ErrorSuppress.php | 35 ++ lib/PHPCfg/ParserHandler/Expr/Exit_.php | 33 + lib/PHPCfg/ParserHandler/Expr/FuncCall.php | 65 ++ lib/PHPCfg/ParserHandler/Expr/Include_.php | 27 + lib/PHPCfg/ParserHandler/Expr/InstanceOf_.php | 32 + lib/PHPCfg/ParserHandler/Expr/Isset_.php | 27 + lib/PHPCfg/ParserHandler/Expr/MethodCall.php | 29 + lib/PHPCfg/ParserHandler/Expr/New_.php | 36 ++ .../ParserHandler/Expr/PropertyFetch.php | 27 + lib/PHPCfg/ParserHandler/Expr/ShellExec.php | 33 + lib/PHPCfg/ParserHandler/Expr/StaticCall.php | 29 + .../Expr/StaticPropertyFetch.php | 27 + lib/PHPCfg/ParserHandler/Expr/Ternary.php | 63 ++ lib/PHPCfg/ParserHandler/Expr/Throw_.php | 30 + lib/PHPCfg/ParserHandler/Expr/Yield_.php | 3 +- lib/PHPCfg/ParserHandler/Stmt/Do_.php | 5 +- lib/PHPCfg/ParserHandler/Stmt/For_.php | 43 ++ lib/PHPCfg/ParserHandler/Stmt/Foreach_.php | 6 +- lib/PHPCfg/ParserHandler/Stmt/If_.php | 6 +- test/code/cast.test | 14 + test/code/unary.test | 30 + 36 files changed, 1079 insertions(+), 601 deletions(-) create mode 100644 lib/PHPCfg/ParserHandler/Batch/AssignOp.php create mode 100644 lib/PHPCfg/ParserHandler/Batch/BinaryOp.php create mode 100644 lib/PHPCfg/ParserHandler/Batch/IncDec.php create mode 100644 lib/PHPCfg/ParserHandler/Batch/Unary.php create mode 100644 lib/PHPCfg/ParserHandler/Expr/ArrowFunction.php create mode 100644 lib/PHPCfg/ParserHandler/Expr/ClassConstFetch.php create mode 100644 lib/PHPCfg/ParserHandler/Expr/Closure_.php create mode 100644 lib/PHPCfg/ParserHandler/Expr/ConstFetch.php create mode 100644 lib/PHPCfg/ParserHandler/Expr/ErrorSuppress.php create mode 100644 lib/PHPCfg/ParserHandler/Expr/Exit_.php create mode 100644 lib/PHPCfg/ParserHandler/Expr/FuncCall.php create mode 100644 lib/PHPCfg/ParserHandler/Expr/Include_.php create mode 100644 lib/PHPCfg/ParserHandler/Expr/InstanceOf_.php create mode 100644 lib/PHPCfg/ParserHandler/Expr/Isset_.php create mode 100644 lib/PHPCfg/ParserHandler/Expr/MethodCall.php create mode 100644 lib/PHPCfg/ParserHandler/Expr/New_.php create mode 100644 lib/PHPCfg/ParserHandler/Expr/PropertyFetch.php create mode 100644 lib/PHPCfg/ParserHandler/Expr/ShellExec.php create mode 100644 lib/PHPCfg/ParserHandler/Expr/StaticCall.php create mode 100644 lib/PHPCfg/ParserHandler/Expr/StaticPropertyFetch.php create mode 100644 lib/PHPCfg/ParserHandler/Expr/Ternary.php create mode 100644 lib/PHPCfg/ParserHandler/Expr/Throw_.php create mode 100644 lib/PHPCfg/ParserHandler/Stmt/For_.php create mode 100644 test/code/cast.test create mode 100644 test/code/unary.test 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 03271e4..3e54391 100755 --- a/lib/PHPCfg/Parser.php +++ b/lib/PHPCfg/Parser.php @@ -25,7 +25,6 @@ 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; @@ -141,7 +140,7 @@ public function parseNodes(array $nodes, Block $block): Block return $end; } - protected function parseFunc(Func $func, array $params, array $stmts): void + public function parseFunc(Func $func, array $params, array $stmts): void { // Switch to new function context $prevCtx = $this->ctx; @@ -189,7 +188,7 @@ protected function parseFunc(Func $func, array $params, array $stmts): void $this->ctx = $prevCtx; } - protected function parseNode(Node $node): void + public function parseNode(Node $node): void { if ($node instanceof Expr) { $this->parseExprNode($node); @@ -321,32 +320,7 @@ protected function parseStmt_Declare(Stmt\Declare_ $node): void - protected function parseStmt_For(Stmt\For_ $node): void - { - $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; - } @@ -706,7 +680,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) { @@ -731,6 +705,9 @@ public function parseExprNode($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); } @@ -771,111 +748,19 @@ public function parseExprNode($expr) if ($expr instanceof Node\InterpolatedStringPart) { return new Literal($expr->value); } - if ($expr instanceof 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 AstBinaryOp) { - 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 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 (isset($this->handlers[$expr->getType()])) { return $this->handlers[$expr->getType()]->handleExpr($expr); - } elseif (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; - } + } elseif ($this->handlers['Batch_Unary']->supports($expr)) { + return $this->handlers['Batch_Unary']->handleExpr($expr); + } elseif ($this->handlers['Batch_AssignOp']->supports($expr)) { + return $this->handlers['Batch_AssignOp']->handleExpr($expr); + } elseif ($this->handlers['Batch_BinaryOp']->supports($expr)) { + return $this->handlers['Batch_BinaryOp']->handleExpr($expr); + } elseif ($this->handlers['Batch_IncDec']->supports($expr)) { + return $this->handlers['Batch_IncDec']->handleExpr($expr); } else { throw new RuntimeException('Unknown Expr Type ' . $expr->getType()); } @@ -883,14 +768,9 @@ public function parseExprNode($expr) throw new RuntimeException('Invalid state, should never happen'); } - protected function parseArg(Node\Arg $expr) - { - return $this->readVariable($this->parseExprNode($expr->value)); - } - public function parseAttribute(Node\Attribute $attr) { - $args = array_map([$this, 'parseArg'], $attr->args); + $args = $this->parseExprList($attr->args); return new Op\Attributes\Attribute($this->readVariable($this->parseExprNode($attr->name)), $args, $this->mapAttributes($attr)); } @@ -907,410 +787,6 @@ public function parseAttributeGroups(array $attrGroups) return array_map([$this, 'parseAttributeGroup'], $attrGroups); } - - - - 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_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 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 Literal($assertionFunctions[$lname])), - ); - } - } - - return $op; - } - - protected function parseExpr_Include(Expr\Include_ $expr) - { - return new Op\Expr\Include_($this->readVariable($this->parseExprNode($expr->expr)), $expr->type, $this->mapAttributes($expr)); - } - - protected function parseExpr_Instanceof(Expr\Instanceof_ $expr) - { - $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; - } - - protected function parseExpr_Isset(Expr\Isset_ $expr) - { - return new Op\Expr\Isset_( - $this->parseExprList($expr->vars, self::MODE_READ), - $this->mapAttributes($expr), - ); - } - - 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 Stmt\Class_) { - $this->parseNode($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 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): Operand - { - $var = $this->parseExprNode($expr->var); - $read = $this->readVariable($var); - $write = $this->writeVariable($var); - $this->block->children[] = $op = new Op\Expr\BinaryOp\Plus($read, new 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): Operand - { - $var = $this->parseExprNode($expr->var); - $read = $this->readVariable($var); - $write = $this->writeVariable($var); - $this->block->children[] = $op = new Op\Expr\BinaryOp\Minus($read, new 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): Operand - { - $var = $this->parseExprNode($expr->var); - $read = $this->readVariable($var); - $write = $this->writeVariable($var); - $this->block->children[] = $op = new Op\Expr\BinaryOp\Plus($read, new 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): Op - { - return new Op\Expr\Print_($this->readVariable($this->parseExprNode($expr->expr)), $this->mapAttributes($expr)); - } - - protected function parseExpr_PropertyFetch(Expr\PropertyFetch $expr): Op - { - 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): Op - { - 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): Op - { - 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): Operand - { - $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): Op - { - return new Op\Expr\UnaryMinus($this->readVariable($this->parseExprNode($expr->expr)), $this->mapAttributes($expr)); - } - - protected function parseExpr_UnaryPlus(Expr\UnaryPlus $expr): Op - { - return new Op\Expr\UnaryPlus($this->readVariable($this->parseExprNode($expr->expr)), $this->mapAttributes($expr)); - } - - protected function parseExpr_ShellExec(Expr\ShellExec $expr): Op - { - $this->block->children[] = $arg = new Op\Expr\ConcatList( - $this->parseExprList($expr->parts, self::MODE_READ), - $this->mapAttributes($expr), - ); - - return new Op\Expr\FuncCall( - new Literal('shell_exec'), - [$arg->result], - $this->mapAttributes($expr), - ); - } - public function processAssertions(Operand $op, Block $if, Block $else): void { $block = $this->block; @@ -1478,43 +954,7 @@ private function parseParameterList(Func $func, array $params): array return $result; } - private function parseShortCircuiting(AstBinaryOp $expr, $isOr): Operand - { - $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; - } public function mapAttributes(Node $expr): array { diff --git a/lib/PHPCfg/ParserHandler.php b/lib/PHPCfg/ParserHandler.php index cf66807..ca9dbbb 100644 --- a/lib/PHPCfg/ParserHandler.php +++ b/lib/PHPCfg/ParserHandler.php @@ -11,6 +11,7 @@ namespace PHPCfg; +use LogicException; use PhpParser\Node; abstract class ParserHandler @@ -24,11 +25,11 @@ public function __construct(Parser $parser) public function handleExpr(Node\Expr $expr): Operand { - throw new \LogicException("Expr " . $expr->getType() . " not Implemented Yet"); + throw new LogicException("Expr " . $expr->getType() . " not Implemented Yet"); } public function handleStmt(Node\Stmt $stmt): void { - throw new \LogicException("Stmt " . $stmt->getType() . " not Implemented Yet"); + throw new LogicException("Stmt " . $stmt->getType() . " not Implemented Yet"); } public function getName(): string @@ -73,6 +74,15 @@ protected function mapAttributes(Node $expr): array 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); + break; + case 'Stmt_Jump': + $op->target->addParent($this->parser->block); + break; + } } protected function addExpr(Op\Expr $expr): Operand diff --git a/lib/PHPCfg/ParserHandler/Batch/AssignOp.php b/lib/PHPCfg/ParserHandler/Batch/AssignOp.php new file mode 100644 index 0000000..c6c5693 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Batch/AssignOp.php @@ -0,0 +1,58 @@ + 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 supports(Node $expr): bool + { + return isset(self::MAP[$expr->getType()]); + } + + public function handleExpr(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..d47f677 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Batch/BinaryOp.php @@ -0,0 +1,118 @@ + '', + '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_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 supports(Node $expr): bool + { + return isset(self::MAP[$expr->getType()]); + } + + public function handleExpr(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(); + $endBlock = $this->createBlockWithCatchTarget(); + + $left = $this->parser->readVariable($this->parser->parseExprNode($expr->left)); + $if = $isOr ? $endBlock : $longBlock; + $else = $isOr ? $longBlock : $endBlock; + + $this->addOp(new Op\Stmt\JumpIf($left, $if, $else)); + $longBlock->addParent($this->block()); + $endBlock->addParent($this->block()); + + $this->block($longBlock); + $right = $this->parser->readVariable($this->parser->parseExprNode($expr->right)); + $boolCast = new Op\Expr\Cast\Bool_($right); + $this->addOp($boolCast); + $this->addOp(new Op\Stmt\Jump($endBlock)); + $endBlock->addParent($this->block()); + + $this->block($endBlock); + $phi = new Op\Phi($result, ['block' => $this->block()]); + $phi->addOperand(new Operand\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; + } +} diff --git a/lib/PHPCfg/ParserHandler/Batch/IncDec.php b/lib/PHPCfg/ParserHandler/Batch/IncDec.php new file mode 100644 index 0000000..b44afce --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Batch/IncDec.php @@ -0,0 +1,56 @@ + 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 supports(Node $expr): bool + { + return isset(self::MAP[$expr->getType()]); + } + + public function handleExpr(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/Unary.php b/lib/PHPCfg/ParserHandler/Batch/Unary.php new file mode 100644 index 0000000..c91320d --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Batch/Unary.php @@ -0,0 +1,64 @@ + 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_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 supports(Node $expr): bool + { + return isset(self::MAP[$expr->getType()]); + } + + public function handleExpr(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 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/ArrayDimFetch.php b/lib/PHPCfg/ParserHandler/Expr/ArrayDimFetch.php index 2b001f4..ee72d5e 100644 --- a/lib/PHPCfg/ParserHandler/Expr/ArrayDimFetch.php +++ b/lib/PHPCfg/ParserHandler/Expr/ArrayDimFetch.php @@ -11,7 +11,6 @@ use PHPCfg\Op; use PHPCfg\Operand; -use PHPCfg\Parser; use PHPCfg\ParserHandler; use PhpParser\Node\Expr; @@ -29,4 +28,4 @@ public function handleExpr(Expr $expr): Operand return $this->addExpr(new Op\Expr\ArrayDimFetch($v, $d, $this->mapAttributes($expr))); } -} \ No newline at end of file +} diff --git a/lib/PHPCfg/ParserHandler/Expr/Array_.php b/lib/PHPCfg/ParserHandler/Expr/Array_.php index 4001b43..d94d7a6 100644 --- a/lib/PHPCfg/ParserHandler/Expr/Array_.php +++ b/lib/PHPCfg/ParserHandler/Expr/Array_.php @@ -11,7 +11,6 @@ use PHPCfg\Op; use PHPCfg\Operand; -use PHPCfg\Parser; use PHPCfg\ParserHandler; use PhpParser\Node\Expr; @@ -37,4 +36,4 @@ public function handleExpr(Expr $expr): Operand return $this->addExpr($array = new Op\Expr\Array_($keys, $values, $byRef, $this->mapAttributes($expr))); } -} \ No newline at end of file +} diff --git a/lib/PHPCfg/ParserHandler/Expr/ArrowFunction.php b/lib/PHPCfg/ParserHandler/Expr/ArrowFunction.php new file mode 100644 index 0000000..6442098 --- /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 index ae0b087..a24fd0e 100644 --- a/lib/PHPCfg/ParserHandler/Expr/AssignRef.php +++ b/lib/PHPCfg/ParserHandler/Expr/AssignRef.php @@ -11,7 +11,6 @@ use PHPCfg\Op; use PHPCfg\Operand; -use PHPCfg\Parser; use PHPCfg\ParserHandler; use PhpParser\Node\Expr; @@ -25,4 +24,4 @@ public function handleExpr(Expr $expr): Operand return $this->addExpr(new Op\Expr\AssignRef($v, $e, $this->mapAttributes($expr))); } -} \ No newline at end of file +} diff --git a/lib/PHPCfg/ParserHandler/Expr/ClassConstFetch.php b/lib/PHPCfg/ParserHandler/Expr/ClassConstFetch.php new file mode 100644 index 0000000..4bcaebc --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/ClassConstFetch.php @@ -0,0 +1,28 @@ +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 index ec718e9..1e2ca31 100644 --- a/lib/PHPCfg/ParserHandler/Expr/Clone_.php +++ b/lib/PHPCfg/ParserHandler/Expr/Clone_.php @@ -11,7 +11,6 @@ use PHPCfg\Op; use PHPCfg\Operand; -use PHPCfg\Parser; use PHPCfg\ParserHandler; use PhpParser\Node; @@ -20,10 +19,10 @@ class Clone_ extends ParserHandler public function handle(Node $expr): Operand { return $this->addExpr(new Op\Expr\Clone_( - $this->parser->readVariable($this->parser->parseExprNode($expr->expr)), + $this->parser->readVariable($this->parser->parseExprNode($expr->expr)), $this->mapAttributes($expr) )); } -} \ No newline at end of file +} diff --git a/lib/PHPCfg/ParserHandler/Expr/Closure_.php b/lib/PHPCfg/ParserHandler/Expr/Closure_.php new file mode 100644 index 0000000..b46b645 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/Closure_.php @@ -0,0 +1,48 @@ +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..2733606 --- /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..ea36a7d --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/ErrorSuppress.php @@ -0,0 +1,35 @@ +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..1f0e03f --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/Exit_.php @@ -0,0 +1,33 @@ +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..eab2673 --- /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..3b4a891 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/Include_.php @@ -0,0 +1,27 @@ +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..b91c052 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/InstanceOf_.php @@ -0,0 +1,32 @@ +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..38c98fa --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/Isset_.php @@ -0,0 +1,27 @@ +addExpr(new Op\Expr\Isset_( + $this->parser->parseExprList($expr->vars, Parser::MODE_READ), + $this->mapAttributes($expr), + )); + } +} diff --git a/lib/PHPCfg/ParserHandler/Expr/MethodCall.php b/lib/PHPCfg/ParserHandler/Expr/MethodCall.php new file mode 100644 index 0000000..33e55a7 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/MethodCall.php @@ -0,0 +1,29 @@ +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..7f6bc2a --- /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..7f1c224 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/PropertyFetch.php @@ -0,0 +1,27 @@ +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..408b614 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/ShellExec.php @@ -0,0 +1,33 @@ +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..2aece2d --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/StaticCall.php @@ -0,0 +1,29 @@ +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..fbc836b --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/StaticPropertyFetch.php @@ -0,0 +1,27 @@ +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..0b1bcc0 --- /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->parser->processAssertions($cond, $ifBlock, $elseBlock); + + $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..d7090bc --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/Throw_.php @@ -0,0 +1,30 @@ +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/Yield_.php b/lib/PHPCfg/ParserHandler/Expr/Yield_.php index 45ccf23..6adca46 100644 --- a/lib/PHPCfg/ParserHandler/Expr/Yield_.php +++ b/lib/PHPCfg/ParserHandler/Expr/Yield_.php @@ -11,7 +11,6 @@ use PHPCfg\Op; use PHPCfg\Operand; -use PHPCfg\Parser; use PHPCfg\ParserHandler; use PhpParser\Node\Expr; @@ -31,4 +30,4 @@ public function handleExpr(Expr $expr): Operand return $this->addExpr(new Op\Expr\Yield_($value, $key, $this->mapAttributes($expr))); } -} \ No newline at end of file +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/Do_.php b/lib/PHPCfg/ParserHandler/Stmt/Do_.php index 0381e53..25bf858 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/Do_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/Do_.php @@ -17,18 +17,15 @@ class Do_ extends ParserHandler { public function handleStmt(Stmt $node): void { - $loopBody = $this->createBlockWithParent(); + $loopBody = $this->createBlockWithCatchTarget(); $loopEnd = $this->createBlockWithCatchTarget(); $this->addOp(new Op\Stmt\Jump($loopBody, $this->mapAttributes($node))); - $loopBody->addParent($this->block()); $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); - $loopBody->addParent($this->block()); - $loopEnd->addParent($this->block()); $this->block($loopEnd); } diff --git a/lib/PHPCfg/ParserHandler/Stmt/For_.php b/lib/PHPCfg/ParserHandler/Stmt/For_.php new file mode 100644 index 0000000..e8ab784 --- /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->parser->processAssertions($cond, $loopBody, $loopEnd); + + $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/Foreach_.php b/lib/PHPCfg/ParserHandler/Stmt/Foreach_.php index 09a434a..2c2603a 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/Foreach_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/Foreach_.php @@ -12,8 +12,8 @@ use PHPCfg\Op; use PHPCfg\ParserHandler; use PHPCfg\ParserHandler\Expr\Assign; -use PhpParser\Node\Stmt; use PhpParser\Node\Expr; +use PhpParser\Node\Stmt; class Foreach_ extends ParserHandler { @@ -23,7 +23,7 @@ public function handleStmt(Stmt $node): void $iterable = $this->parser->readVariable($this->parser->parseExprNode($node->expr)); $this->addOp(new Op\Iterator\Reset($iterable, $attrs)); - $loopInit = $this->createBlockWithParent(); + $loopInit = $this->createBlockWithCatchTarget(); $loopBody = $this->createBlockWithCatchTarget(); $loopEnd = $this->createBlockWithCatchTarget(); @@ -55,8 +55,6 @@ public function handleStmt(Stmt $node): void $this->block($this->parser->parseNodes($node->stmts, $this->block())); $this->addOp(new Op\Stmt\Jump($loopInit, $attrs)); - $loopInit->addParent($this->block()); - $this->block($loopEnd); } diff --git a/lib/PHPCfg/ParserHandler/Stmt/If_.php b/lib/PHPCfg/ParserHandler/Stmt/If_.php index 97a4a6b..7bc7d37 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/If_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/If_.php @@ -27,8 +27,8 @@ protected function parseIf(Stmt $node, Block $endBlock): void { $attrs = $this->mapAttributes($node); $cond = $this->parser->readVariable($this->parser->parseExprNode($node->cond)); - $ifBlock = $this->createBlockWithParent(); - $elseBlock = $this->createBlockWithParent(); + $ifBlock = $this->createBlockWithCatchTarget(); + $elseBlock = $this->createBlockWithCatchTarget(); $this->addOp(new Op\Stmt\JumpIf($cond, $ifBlock, $elseBlock, $attrs)); $this->parser->processAssertions($cond, $ifBlock, $elseBlock); @@ -36,7 +36,6 @@ protected function parseIf(Stmt $node, Block $endBlock): void $this->block($this->parser->parseNodes($node->stmts, $ifBlock)); $this->addOp(new Op\Stmt\Jump($endBlock, $attrs)); - $endBlock->addParent($this->block()); $this->block($elseBlock); @@ -48,7 +47,6 @@ protected function parseIf(Stmt $node, Block $endBlock): void $this->block($this->parser->parseNodes($node->else->stmts, $this->block())); } $this->addOp(new Op\Stmt\Jump($endBlock, $attrs)); - $endBlock->addParent($this->block()); } } } 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/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 From a0e9aaa2580793a454c4c2a651525e096d97bef2 Mon Sep 17 00:00:00 2001 From: Anthony Ferrara Date: Fri, 29 Aug 2025 22:24:41 -0400 Subject: [PATCH 06/14] Refactoring more STMT and batch away --- lib/PHPCfg/Parser.php | 521 +----------------- lib/PHPCfg/ParserHandler.php | 5 + lib/PHPCfg/ParserHandler/Batch/AssignOp.php | 5 + lib/PHPCfg/ParserHandler/Batch/BinaryOp.php | 5 + lib/PHPCfg/ParserHandler/Batch/IncDec.php | 5 + lib/PHPCfg/ParserHandler/Batch/Nop.php | 35 ++ lib/PHPCfg/ParserHandler/Batch/Unary.php | 5 + lib/PHPCfg/ParserHandler/Stmt/ClassConst.php | 38 ++ lib/PHPCfg/ParserHandler/Stmt/ClassMethod.php | 57 ++ lib/PHPCfg/ParserHandler/Stmt/Declare_.php | 14 + lib/PHPCfg/ParserHandler/Stmt/Function_.php | 35 ++ lib/PHPCfg/ParserHandler/Stmt/Global_.php | 28 + lib/PHPCfg/ParserHandler/Stmt/Goto_.php | 29 + .../ParserHandler/Stmt/HaltCompiler.php | 26 + lib/PHPCfg/ParserHandler/Stmt/InlineHTML.php | 25 + lib/PHPCfg/ParserHandler/Stmt/Interface_.php | 34 ++ lib/PHPCfg/ParserHandler/Stmt/Label.php | 43 ++ lib/PHPCfg/ParserHandler/Stmt/Namespace_.php | 22 + lib/PHPCfg/ParserHandler/Stmt/Property.php | 49 ++ lib/PHPCfg/ParserHandler/Stmt/Return_.php | 29 + lib/PHPCfg/ParserHandler/Stmt/Static_.php | 44 ++ lib/PHPCfg/ParserHandler/Stmt/Switch_.php | 124 +++++ lib/PHPCfg/ParserHandler/Stmt/TraitUse.php | 50 ++ lib/PHPCfg/ParserHandler/Stmt/Trait_.php | 31 ++ lib/PHPCfg/ParserHandler/Stmt/TryCatch.php | 71 +++ lib/PHPCfg/ParserHandler/Stmt/Unset_.php | 26 + lib/PHPCfg/ParserHandler/Stmt/While_.php | 34 ++ test/code/while.test | 35 ++ 28 files changed, 928 insertions(+), 497 deletions(-) create mode 100644 lib/PHPCfg/ParserHandler/Batch/Nop.php create mode 100644 lib/PHPCfg/ParserHandler/Stmt/ClassConst.php create mode 100644 lib/PHPCfg/ParserHandler/Stmt/ClassMethod.php create mode 100644 lib/PHPCfg/ParserHandler/Stmt/Declare_.php create mode 100644 lib/PHPCfg/ParserHandler/Stmt/Function_.php create mode 100644 lib/PHPCfg/ParserHandler/Stmt/Global_.php create mode 100644 lib/PHPCfg/ParserHandler/Stmt/Goto_.php create mode 100644 lib/PHPCfg/ParserHandler/Stmt/HaltCompiler.php create mode 100644 lib/PHPCfg/ParserHandler/Stmt/InlineHTML.php create mode 100644 lib/PHPCfg/ParserHandler/Stmt/Interface_.php create mode 100644 lib/PHPCfg/ParserHandler/Stmt/Label.php create mode 100644 lib/PHPCfg/ParserHandler/Stmt/Namespace_.php create mode 100644 lib/PHPCfg/ParserHandler/Stmt/Property.php create mode 100644 lib/PHPCfg/ParserHandler/Stmt/Return_.php create mode 100644 lib/PHPCfg/ParserHandler/Stmt/Static_.php create mode 100644 lib/PHPCfg/ParserHandler/Stmt/Switch_.php create mode 100644 lib/PHPCfg/ParserHandler/Stmt/TraitUse.php create mode 100644 lib/PHPCfg/ParserHandler/Stmt/Trait_.php create mode 100644 lib/PHPCfg/ParserHandler/Stmt/TryCatch.php create mode 100644 lib/PHPCfg/ParserHandler/Stmt/Unset_.php create mode 100644 lib/PHPCfg/ParserHandler/Stmt/While_.php create mode 100644 test/code/while.test diff --git a/lib/PHPCfg/Parser.php b/lib/PHPCfg/Parser.php index 3e54391..662c2bf 100755 --- a/lib/PHPCfg/Parser.php +++ b/lib/PHPCfg/Parser.php @@ -12,20 +12,12 @@ namespace PHPCfg; use LogicException; -use PHPCfg\Op\Stmt\Jump; -use PHPCfg\Op\Stmt\JumpIf; -use PHPCfg\Op\Stmt\TraitUse; -use PHPCfg\Op\Stmt\Try_; 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\Stmt; use PhpParser\NodeTraverser as AstTraverser; use PhpParser\Parser as AstParser; use RecursiveDirectoryIterator; @@ -60,6 +52,7 @@ class Parser public $anonId = 0; protected array $handlers = []; + protected array $batchHandlers = []; public function __construct(AstParser $astParser, ?AstTraverser $astTraverser = null) { @@ -74,6 +67,15 @@ public function __construct(AstParser $astParser, ?AstTraverser $astTraverser = $this->loadHandlers(); } + public function addHandler(string $name, ParserHandler $handler): void + { + if ($handler->isBatch()) { + $this->batchHandlers[$name] = $handler; + } else { + $this->handlers[$name] = $handler; + } + } + protected function loadHandlers(): void { $it = new RecursiveIteratorIterator( @@ -91,7 +93,7 @@ protected function loadHandlers(): void $class = __NAMESPACE__ . str_replace("/", "\\", $class); $class = substr($class, 0, -4); $obj = new $class($this); - $this->handlers[$obj->getName()] = $obj; + $this->addHandler($obj->getName(), $obj); } } @@ -200,10 +202,12 @@ public function parseNode(Node $node): void if (isset($this->handlers[$type])) { $this->handlers[$type]->handleStmt($node); return; - } elseif (method_exists($this, 'parse' . $type)) { - $this->{'parse' . $type}($node); - - return; + } + foreach ($this->batchHandlers as $handler) { + if ($handler->supports($node)) { + $handler->handleStmt($node); + return; + } } throw new RuntimeException('Unknown Node Encountered : ' . $type); @@ -256,424 +260,6 @@ public function parseTypeNode(?Node $node): Op\Type throw new LogicException("Unknown type node: " . $node->getType()); } - protected function parseStmt_ClassConst(Stmt\ClassConst $node): void - { - 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): void - { - 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_Declare(Stmt\Declare_ $node): void - { - // TODO - } - - - - - - - - protected function parseStmt_Function(Stmt\Function_ $node): void - { - $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): void - { - 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): void - { - $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): void - { - // ignore use statements, since names are already resolved - } - - protected function parseStmt_HaltCompiler(Stmt\HaltCompiler $node): void - { - $this->block->children[] = new Op\Terminal\Echo_( - $this->readVariable(new Literal($node->remaining)), - $this->mapAttributes($node), - ); - } - - protected function parseStmt_InlineHTML(Stmt\InlineHTML $node): void - { - $this->block->children[] = new Op\Terminal\Echo_($this->parseExprNode($node->value), $this->mapAttributes($node)); - } - - protected function parseStmt_Interface(Stmt\Interface_ $node): void - { - $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): void - { - 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 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): void - { - $this->currentNamespace = $node->name; - $this->block = $this->parseNodes($node->stmts, $this->block); - } - - protected function parseStmt_Nop(Stmt\Nop $node): void - { - // Nothing to see here, move along - } - - protected function parseStmt_Property(Stmt\Property $node): void - { - $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): void - { - $expr = null; - if ($node->expr) { - $expr = $this->readVariable($this->parseExprNode($node->expr)); - } - $this->block->children[] = new 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): void - { - 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): void - { - 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), - ); - - $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), - ); - } 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), - ); - } - } - $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; - } - /** * @param Node[] $expr * @param int $readWrite @@ -753,19 +339,13 @@ public function parseExprNode($expr) if (isset($this->handlers[$expr->getType()])) { return $this->handlers[$expr->getType()]->handleExpr($expr); - } elseif ($this->handlers['Batch_Unary']->supports($expr)) { - return $this->handlers['Batch_Unary']->handleExpr($expr); - } elseif ($this->handlers['Batch_AssignOp']->supports($expr)) { - return $this->handlers['Batch_AssignOp']->handleExpr($expr); - } elseif ($this->handlers['Batch_BinaryOp']->supports($expr)) { - return $this->handlers['Batch_BinaryOp']->handleExpr($expr); - } elseif ($this->handlers['Batch_IncDec']->supports($expr)) { - return $this->handlers['Batch_IncDec']->handleExpr($expr); - } else { - throw new RuntimeException('Unknown Expr Type ' . $expr->getType()); } - - throw new RuntimeException('Invalid state, should never happen'); + foreach ($this->batchHandlers as $handler) { + if ($handler->supports($expr)) { + return $handler->handleExpr($expr); + } + } + throw new RuntimeException('Unknown Expr Type ' . $expr->getType()); } public function parseAttribute(Node\Attribute $attr) @@ -827,61 +407,8 @@ protected function throwUndefinedLabelError(): void } } - private function switchCanUseJumptable(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(Stmt\Switch_ $node): void - { - $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): Operand { @@ -921,7 +448,7 @@ private function parseScalarNode(Node\Scalar $scalar): Operand } } - private function parseParameterList(Func $func, array $params): array + public function parseParameterList(Func $func, array $params): array { if (empty($params)) { return []; diff --git a/lib/PHPCfg/ParserHandler.php b/lib/PHPCfg/ParserHandler.php index ca9dbbb..93aa0ba 100644 --- a/lib/PHPCfg/ParserHandler.php +++ b/lib/PHPCfg/ParserHandler.php @@ -32,6 +32,11 @@ public function handleStmt(Node\Stmt $stmt): void throw new LogicException("Stmt " . $stmt->getType() . " not Implemented Yet"); } + public function isBatch(): bool + { + return false; + } + public function getName(): string { $name = str_replace([__CLASS__ . '\\', '_'], '', get_class($this)); diff --git a/lib/PHPCfg/ParserHandler/Batch/AssignOp.php b/lib/PHPCfg/ParserHandler/Batch/AssignOp.php index c6c5693..6c9ea72 100644 --- a/lib/PHPCfg/ParserHandler/Batch/AssignOp.php +++ b/lib/PHPCfg/ParserHandler/Batch/AssignOp.php @@ -34,6 +34,11 @@ class AssignOp extends ParserHandler 'Expr_AssignOp_ShiftRight' => Op\Expr\BinaryOp\ShiftRight::class, ]; + public function isBatch(): bool + { + return true; + } + public function supports(Node $expr): bool { return isset(self::MAP[$expr->getType()]); diff --git a/lib/PHPCfg/ParserHandler/Batch/BinaryOp.php b/lib/PHPCfg/ParserHandler/Batch/BinaryOp.php index d47f677..255c8fd 100644 --- a/lib/PHPCfg/ParserHandler/Batch/BinaryOp.php +++ b/lib/PHPCfg/ParserHandler/Batch/BinaryOp.php @@ -49,6 +49,11 @@ class BinaryOp extends ParserHandler 'Expr_BinaryOp_Spaceship' => Op\Expr\BinaryOp\Spaceship::class, ]; + public function isBatch(): bool + { + return true; + } + public function supports(Node $expr): bool { return isset(self::MAP[$expr->getType()]); diff --git a/lib/PHPCfg/ParserHandler/Batch/IncDec.php b/lib/PHPCfg/ParserHandler/Batch/IncDec.php index b44afce..e7035f9 100644 --- a/lib/PHPCfg/ParserHandler/Batch/IncDec.php +++ b/lib/PHPCfg/ParserHandler/Batch/IncDec.php @@ -25,6 +25,11 @@ class IncDec extends ParserHandler 'Expr_PreInc' => Op\Expr\BinaryOp\Plus::class, ]; + public function isBatch(): bool + { + return true; + } + public function supports(Node $expr): bool { return isset(self::MAP[$expr->getType()]); diff --git a/lib/PHPCfg/ParserHandler/Batch/Nop.php b/lib/PHPCfg/ParserHandler/Batch/Nop.php new file mode 100644 index 0000000..681c80c --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Batch/Nop.php @@ -0,0 +1,35 @@ + true, + 'Stmt_Nop' => true, + 'Stmt_Use' => true, + ]; + + public function isBatch(): bool + { + return true; + } + + public function supports(Node $expr): bool + { + return isset(self::MAP[$expr->getType()]); + } + + public function handleStmt(Node\Stmt $node): void {} +} diff --git a/lib/PHPCfg/ParserHandler/Batch/Unary.php b/lib/PHPCfg/ParserHandler/Batch/Unary.php index c91320d..8e96ae9 100644 --- a/lib/PHPCfg/ParserHandler/Batch/Unary.php +++ b/lib/PHPCfg/ParserHandler/Batch/Unary.php @@ -35,6 +35,11 @@ class Unary extends ParserHandler 'Expr_UnaryPlus' => Op\Expr\UnaryPlus::class, ]; + public function isBatch(): bool + { + return true; + } + public function supports(Node $expr): bool { return isset(self::MAP[$expr->getType()]); diff --git a/lib/PHPCfg/ParserHandler/Stmt/ClassConst.php b/lib/PHPCfg/ParserHandler/Stmt/ClassConst.php new file mode 100644 index 0000000..56db213 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/ClassConst.php @@ -0,0 +1,38 @@ +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..6967525 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/ClassMethod.php @@ -0,0 +1,57 @@ +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/Declare_.php b/lib/PHPCfg/ParserHandler/Stmt/Declare_.php new file mode 100644 index 0000000..339c448 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Declare_.php @@ -0,0 +1,14 @@ +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..4b63644 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Global_.php @@ -0,0 +1,28 @@ +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..066af49 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Goto_.php @@ -0,0 +1,29 @@ +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..9067658 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/HaltCompiler.php @@ -0,0 +1,26 @@ +addOp(new Op\Terminal\Echo_( + $this->parser->readVariable(new Operand\Literal($node->remaining)), + $this->mapAttributes($node), + )); + } +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/InlineHTML.php b/lib/PHPCfg/ParserHandler/Stmt/InlineHTML.php new file mode 100644 index 0000000..9f6d2d5 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/InlineHTML.php @@ -0,0 +1,25 @@ +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..16eabaa --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Interface_.php @@ -0,0 +1,34 @@ +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..dc41c92 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Label.php @@ -0,0 +1,43 @@ +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..1b45cff --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Namespace_.php @@ -0,0 +1,22 @@ +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..9fe7e3a --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Property.php @@ -0,0 +1,49 @@ +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..e6a573e --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Return_.php @@ -0,0 +1,29 @@ +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..60a5ae8 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Static_.php @@ -0,0 +1,44 @@ +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..0e7b562 --- /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(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(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..9636f2c --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/TraitUse.php @@ -0,0 +1,50 @@ +traits as $trait_) { + $traits[] = new Operand\Literal($trait_->toCodeString()); + } + foreach ($node->adaptations as $adaptation) { + if ($adaptation instanceof 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 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..d2b8aba --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Trait_.php @@ -0,0 +1,31 @@ +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..7825041 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/TryCatch.php @@ -0,0 +1,71 @@ +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..5f86b8a --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Stmt/Unset_.php @@ -0,0 +1,26 @@ +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..5b3578c --- /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->parser->processAssertions($cond, $loopBody, $loopEnd); + + $this->block($this->parser->parseNodes($node->stmts, $loopBody)); + $this->addOp(new Op\Stmt\Jump($loopInit, $this->mapAttributes($node))); + $this->block($loopEnd); + } +} 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 @@ + Date: Fri, 29 Aug 2025 23:32:42 -0400 Subject: [PATCH 07/14] A bunch more refactoring. Fixed short circuit bug that existed for quite a while --- lib/PHPCfg/Parser.php | 168 ++++--------------- lib/PHPCfg/ParserHandler.php | 21 +++ lib/PHPCfg/ParserHandler/Batch/BinaryOp.php | 16 +- lib/PHPCfg/ParserHandler/Batch/Scalar.php | 75 +++++++++ lib/PHPCfg/ParserHandler/Expr/Ternary.php | 1 - lib/PHPCfg/ParserHandler/Expr/Variable.php | 40 +++++ lib/PHPCfg/ParserHandler/Stmt/Class_.php | 2 +- lib/PHPCfg/ParserHandler/Stmt/For_.php | 1 - lib/PHPCfg/ParserHandler/Stmt/Foreach_.php | 8 +- lib/PHPCfg/ParserHandler/Stmt/If_.php | 1 - lib/PHPCfg/ParserHandler/Stmt/Interface_.php | 2 +- lib/PHPCfg/ParserHandler/Stmt/While_.php | 1 - test/code/and.test | 2 +- test/code/or.test | 2 +- test/code/type_assert.test | 58 ++++--- 15 files changed, 217 insertions(+), 181 deletions(-) create mode 100644 lib/PHPCfg/ParserHandler/Batch/Scalar.php create mode 100644 lib/PHPCfg/ParserHandler/Expr/Variable.php diff --git a/lib/PHPCfg/Parser.php b/lib/PHPCfg/Parser.php index 662c2bf..10361c3 100755 --- a/lib/PHPCfg/Parser.php +++ b/lib/PHPCfg/Parser.php @@ -97,21 +97,12 @@ protected function loadHandlers(): void } } - /** - * @param string $code - * @param string $fileName - * @returns Script - */ - public function parse($code, $fileName) + 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); @@ -213,14 +204,9 @@ public function parseNode(Node $node): void throw new RuntimeException('Unknown Node Encountered : ' . $type); } - public 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); } public function parseTypeNode(?Node $node): Op\Type @@ -228,34 +214,29 @@ 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), - ); + 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), + ); + case 'NullableType': + return new Op\Type\Nullable( + $this->parseTypeNode($node->type), + $this->mapAttributes($node), + ); + case 'UnionType': + return new Op\Type\Union( + $this->parseTypeList(...$node->types), + $this->mapAttributes($node), + ); + case 'Identifier': + return new Op\Type\Literal( + $node->name, + $this->mapAttributes($node), + ); } throw new LogicException("Unknown type node: " . $node->getType()); } @@ -278,17 +259,16 @@ public function parseExprList(array $expr, $readWrite = self::MODE_NONE): array return $vars; } - public 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) { @@ -297,28 +277,6 @@ public function parseExprNode($expr) if ($expr instanceof Node\Identifier) { return new Literal($expr->name); } - if ($expr instanceof 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) { @@ -328,15 +286,11 @@ public 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); } - $method = 'parse' . $expr->getType(); - if (isset($this->handlers[$expr->getType()])) { return $this->handlers[$expr->getType()]->handleExpr($expr); } @@ -367,27 +321,7 @@ public function parseAttributeGroups(array $attrGroups) return array_map([$this, 'parseAttributeGroup'], $attrGroups); } - public 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->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): Assertion + public function readAssertion(Assertion $assert): Assertion { if ($assert->value instanceof Operand) { return new $assert($this->readVariable($assert->value)); @@ -408,46 +342,6 @@ protected function throwUndefinedLabelError(): void } - - - private function parseScalarNode(Node\Scalar $scalar): Operand - { - 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()); - } - } - public function parseParameterList(Func $func, array $params): array { if (empty($params)) { diff --git a/lib/PHPCfg/ParserHandler.php b/lib/PHPCfg/ParserHandler.php index 93aa0ba..342843f 100644 --- a/lib/PHPCfg/ParserHandler.php +++ b/lib/PHPCfg/ParserHandler.php @@ -83,6 +83,7 @@ protected function addOp(Op $op): void 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); @@ -96,4 +97,24 @@ protected function addExpr(Op\Expr $expr): Operand 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/BinaryOp.php b/lib/PHPCfg/ParserHandler/Batch/BinaryOp.php index 255c8fd..bccdc30 100644 --- a/lib/PHPCfg/ParserHandler/Batch/BinaryOp.php +++ b/lib/PHPCfg/ParserHandler/Batch/BinaryOp.php @@ -87,27 +87,27 @@ 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 ? $endBlock : $longBlock; - $else = $isOr ? $longBlock : $endBlock; + $if = $isOr ? $midBlock : $longBlock; + $else = $isOr ? $longBlock : $midBlock; $this->addOp(new Op\Stmt\JumpIf($left, $if, $else)); - $longBlock->addParent($this->block()); - $endBlock->addParent($this->block()); $this->block($longBlock); $right = $this->parser->readVariable($this->parser->parseExprNode($expr->right)); - $boolCast = new Op\Expr\Cast\Bool_($right); - $this->addOp($boolCast); + $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)); - $endBlock->addParent($this->block()); $this->block($endBlock); $phi = new Op\Phi($result, ['block' => $this->block()]); $phi->addOperand(new Operand\Literal($isOr)); - $phi->addOperand($boolCast->result); + $phi->addOperand($castResult); $this->block()->phi[] = $phi; $mode = $isOr ? Assertion::MODE_UNION : Assertion::MODE_INTERSECTION; diff --git a/lib/PHPCfg/ParserHandler/Batch/Scalar.php b/lib/PHPCfg/ParserHandler/Batch/Scalar.php new file mode 100644 index 0000000..42c6e44 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Batch/Scalar.php @@ -0,0 +1,75 @@ + true, + 'Scalar_Float' => true, + 'Scalar_Int' => true, + 'Scalar_InterpolatedString' => true, + 'Scalar_LNumber' => true, + 'Scalar_String' => true, + ]; + + public function isBatch(): bool + { + return true; + } + + public function supports(Node $expr): bool + { + return isset(self::MAP[$expr->getType()]) || strpos($expr->getType(), 'Scalar_MagicConst_') === 0; + } + + 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->fileName)); + case 'Scalar_MagicConst_File': + return new Operand\Literal($this->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/Expr/Ternary.php b/lib/PHPCfg/ParserHandler/Expr/Ternary.php index 0b1bcc0..9fbe4b1 100644 --- a/lib/PHPCfg/ParserHandler/Expr/Ternary.php +++ b/lib/PHPCfg/ParserHandler/Expr/Ternary.php @@ -26,7 +26,6 @@ public function handleExpr(Expr $expr): Operand $endBlock = $this->createBlockWithCatchTarget(); $this->addOp(new Op\Stmt\JumpIf($cond, $ifBlock, $elseBlock, $attrs)); - $this->parser->processAssertions($cond, $ifBlock, $elseBlock); $this->block($ifBlock); $ifVar = new Operand\Temporary(); diff --git a/lib/PHPCfg/ParserHandler/Expr/Variable.php b/lib/PHPCfg/ParserHandler/Expr/Variable.php new file mode 100644 index 0000000..c021e4d --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/Variable.php @@ -0,0 +1,40 @@ +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) + )); + } +} \ No newline at end of file diff --git a/lib/PHPCfg/ParserHandler/Stmt/Class_.php b/lib/PHPCfg/ParserHandler/Stmt/Class_.php index a3cf756..8aa2064 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/Class_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/Class_.php @@ -25,7 +25,7 @@ public function handleStmt(Stmt $node): void $name, $node->flags, $node->extends ? $this->parser->parseTypeNode($node->extends) : null, - $this->parser->parseTypeList($node->implements), + $this->parser->parseTypeList(...$node->implements), $this->parser->parseNodes($node->stmts, $this->createBlock()), $this->parser->parseAttributeGroups($node->attrGroups), $this->mapAttributes($node), diff --git a/lib/PHPCfg/ParserHandler/Stmt/For_.php b/lib/PHPCfg/ParserHandler/Stmt/For_.php index e8ab784..ea0ebea 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/For_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/For_.php @@ -33,7 +33,6 @@ public function handleStmt(Stmt $node): void $cond = new Operand\Literal(true); } $this->addOp(new Op\Stmt\JumpIf($cond, $loopBody, $loopEnd, $this->mapAttributes($node))); - $this->parser->processAssertions($cond, $loopBody, $loopEnd); $this->block($this->parser->parseNodes($node->stmts, $loopBody)); $this->parser->parseExprList($node->loop, Parser::MODE_READ); diff --git a/lib/PHPCfg/ParserHandler/Stmt/Foreach_.php b/lib/PHPCfg/ParserHandler/Stmt/Foreach_.php index 2c2603a..f97fff4 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/Foreach_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/Foreach_.php @@ -29,11 +29,9 @@ public function handleStmt(Stmt $node): void $this->addOp(new Op\Stmt\Jump($loopInit, $attrs)); - $loopInit->children[] = $validOp = new Op\Iterator\Valid($iterable, $attrs); - $loopInit->children[] = new Op\Stmt\JumpIf($validOp->result, $loopBody, $loopEnd, $attrs); - $this->parser->processAssertions($validOp->result, $loopBody, $loopEnd); - $loopBody->addParent($loopInit); - $loopEnd->addParent($loopInit); + $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); diff --git a/lib/PHPCfg/ParserHandler/Stmt/If_.php b/lib/PHPCfg/ParserHandler/Stmt/If_.php index 7bc7d37..8c7ba85 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/If_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/If_.php @@ -31,7 +31,6 @@ protected function parseIf(Stmt $node, Block $endBlock): void $elseBlock = $this->createBlockWithCatchTarget(); $this->addOp(new Op\Stmt\JumpIf($cond, $ifBlock, $elseBlock, $attrs)); - $this->parser->processAssertions($cond, $ifBlock, $elseBlock); $this->block($this->parser->parseNodes($node->stmts, $ifBlock)); diff --git a/lib/PHPCfg/ParserHandler/Stmt/Interface_.php b/lib/PHPCfg/ParserHandler/Stmt/Interface_.php index 16eabaa..fdbeca3 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/Interface_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/Interface_.php @@ -23,7 +23,7 @@ public function handleStmt(Stmt $node): void $this->parser->currentClass = $name; $this->addOp(new Op\Stmt\Interface_( $name, - $this->parser->parseTypeList($node->extends), + $this->parser->parseTypeList(...$node->extends), $this->parser->parseNodes($node->stmts, $this->createBlock()), $this->parser->parseAttributeGroups($node->attrGroups), $this->mapAttributes($node), diff --git a/lib/PHPCfg/ParserHandler/Stmt/While_.php b/lib/PHPCfg/ParserHandler/Stmt/While_.php index 5b3578c..fac374e 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/While_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/While_.php @@ -25,7 +25,6 @@ public function handleStmt(Stmt $node): void $cond = $this->parser->readVariable($this->parser->parseExprNode($node->cond)); $this->addOp(new Op\Stmt\JumpIf($cond, $loopBody, $loopEnd, $this->mapAttributes($node))); - $this->parser->processAssertions($cond, $loopBody, $loopEnd); $this->block($this->parser->parseNodes($node->stmts, $loopBody)); $this->addOp(new Op\Stmt\Jump($loopInit, $this->mapAttributes($node))); 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/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 From 920262aa1b05f9b1bcf475203bc8bc326f68cad7 Mon Sep 17 00:00:00 2001 From: Anthony Ferrara Date: Fri, 29 Aug 2025 23:46:26 -0400 Subject: [PATCH 08/14] Some fixes to attributes --- lib/PHPCfg/Parser.php | 38 ++++++++----------- lib/PHPCfg/ParserHandler/Batch/Scalar.php | 12 +++--- lib/PHPCfg/ParserHandler/Expr/Variable.php | 2 +- lib/PHPCfg/ParserHandler/Stmt/ClassMethod.php | 2 +- lib/PHPCfg/ParserHandler/Stmt/Class_.php | 2 +- lib/PHPCfg/ParserHandler/Stmt/Function_.php | 2 +- lib/PHPCfg/ParserHandler/Stmt/Interface_.php | 2 +- lib/PHPCfg/ParserHandler/Stmt/Property.php | 2 +- lib/PHPCfg/ParserHandler/Stmt/Trait_.php | 2 +- 9 files changed, 29 insertions(+), 35 deletions(-) diff --git a/lib/PHPCfg/Parser.php b/lib/PHPCfg/Parser.php index 10361c3..e525fc8 100755 --- a/lib/PHPCfg/Parser.php +++ b/lib/PHPCfg/Parser.php @@ -159,7 +159,9 @@ public function parseFunc(Func $func, array $params, array $stmts): void } if ($this->ctx->unresolvedGotos) { - $this->throwUndefinedLabelError(); + foreach ($this->ctx->unresolvedGotos as $name => $_) { + throw new RuntimeException("'goto' to undefined label '{$name}'"); + } } $this->ctx->complete = true; @@ -302,23 +304,21 @@ public function parseExprNode($expr): ?Operand throw new RuntimeException('Unknown Expr Type ' . $expr->getType()); } - public function parseAttribute(Node\Attribute $attr) - { - $args = $this->parseExprList($attr->args); - - return new Op\Attributes\Attribute($this->readVariable($this->parseExprNode($attr->name)), $args, $this->mapAttributes($attr)); - } - - public function parseAttributeGroup(Node\AttributeGroup $attrGroup) + public function parseAttributes(Node\Attribute ...$attrs) { - $attrs = array_map([$this, 'parseAttribute'], $attrGroup->attrs); - - return new Op\Attributes\AttributeGroup($attrs, $this->mapAttributes($attrGroup)); + return array_map(fn($attr) => new Op\Attributes\Attribute( + $this->readVariable($this->parseExprNode($attr->name)), + $this->parseExprList($attr->args), + $this->mapAttributes($attr) + ), $attrs); } - public function parseAttributeGroups(array $attrGroups) + public function parseAttributeGroups(Node\AttributeGroup ...$attrGroups) { - return array_map([$this, 'parseAttributeGroup'], $attrGroups); + return array_map(fn($attrGroup) => new Op\Attributes\AttributeGroup( + $this->parseAttributes(...$attrGroup->attrs), + $this->mapAttributes($attrGroup) + ), $attrGroups); } public function readAssertion(Assertion $assert): Assertion @@ -334,14 +334,6 @@ public function readAssertion(Assertion $assert): Assertion return new $assert($vars, $assert->mode); } - protected function throwUndefinedLabelError(): void - { - foreach ($this->ctx->unresolvedGotos as $name => $_) { - throw new RuntimeException("'goto' to undefined label '{$name}'"); - } - } - - public function parseParameterList(Func $func, array $params): array { if (empty($params)) { @@ -363,7 +355,7 @@ public function parseParameterList(Func $func, array $params): array $this->parseTypeNode($param->type), $param->byRef, $param->variadic, - $this->parseAttributeGroups($param->attrGroups), + $this->parseAttributeGroups(...$param->attrGroups), $defaultVar, $defaultBlock, $this->mapAttributes($param), diff --git a/lib/PHPCfg/ParserHandler/Batch/Scalar.php b/lib/PHPCfg/ParserHandler/Batch/Scalar.php index 42c6e44..438f4e4 100644 --- a/lib/PHPCfg/ParserHandler/Batch/Scalar.php +++ b/lib/PHPCfg/ParserHandler/Batch/Scalar.php @@ -10,10 +10,11 @@ namespace PHPCfg\ParserHandler\Batch; use PHPCfg\Op; -use PHPCfg\ParserHandler; +use PHPCfg\Operand; use PHPCfg\Parser; +use PHPCfg\ParserHandler; use PhpParser\Node; -use PHPCfg\Operand; +use RuntimeException; class Scalar extends ParserHandler { @@ -36,12 +37,13 @@ public function supports(Node $expr): bool return isset(self::MAP[$expr->getType()]) || strpos($expr->getType(), 'Scalar_MagicConst_') === 0; } - public function handleExpr(Node\Expr $scalar): Operand { + 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->parser->parseExprList($scalar->parts, Parser::MODE_READ), $this->mapAttributes($scalar) )); case 'Scalar_Float': @@ -69,7 +71,7 @@ public function handleExpr(Node\Expr $scalar): Operand { return new Operand\Literal('__FUNCTION__'); default: var_dump($scalar); - throw new \RuntimeException('Unknown how to deal with scalar type ' . $scalar->getType()); + throw new RuntimeException('Unknown how to deal with scalar type ' . $scalar->getType()); } } } diff --git a/lib/PHPCfg/ParserHandler/Expr/Variable.php b/lib/PHPCfg/ParserHandler/Expr/Variable.php index c021e4d..3f7d433 100644 --- a/lib/PHPCfg/ParserHandler/Expr/Variable.php +++ b/lib/PHPCfg/ParserHandler/Expr/Variable.php @@ -37,4 +37,4 @@ public function handleExpr(Expr $expr): Operand $this->mapAttributes($expr) )); } -} \ No newline at end of file +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/ClassMethod.php b/lib/PHPCfg/ParserHandler/Stmt/ClassMethod.php index 6967525..3615111 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/ClassMethod.php +++ b/lib/PHPCfg/ParserHandler/Stmt/ClassMethod.php @@ -49,7 +49,7 @@ public function handleStmt(Stmt $node): void (bool) $static, (bool) $final, (bool) $abstract, - $this->parser->parseAttributeGroups($node->attrGroups), + $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 index 8aa2064..9752e5b 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/Class_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/Class_.php @@ -27,7 +27,7 @@ public function handleStmt(Stmt $node): void $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->parser->parseAttributeGroups(...$node->attrGroups), $this->mapAttributes($node), )); $this->parser->currentClass = $old; diff --git a/lib/PHPCfg/ParserHandler/Stmt/Function_.php b/lib/PHPCfg/ParserHandler/Stmt/Function_.php index b0a88d3..e5576a6 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/Function_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/Function_.php @@ -27,7 +27,7 @@ public function handleStmt(Stmt $node): void $this->parser->parseFunc($func, $node->params, $node->stmts, null); $this->addOp($function = new Op\Stmt\Function_( $func, - $this->parser->parseAttributeGroups($node->attrGroups), + $this->parser->parseAttributeGroups(...$node->attrGroups), $this->mapAttributes($node) )); $func->callableOp = $function; diff --git a/lib/PHPCfg/ParserHandler/Stmt/Interface_.php b/lib/PHPCfg/ParserHandler/Stmt/Interface_.php index fdbeca3..96c967e 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/Interface_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/Interface_.php @@ -25,7 +25,7 @@ public function handleStmt(Stmt $node): void $name, $this->parser->parseTypeList(...$node->extends), $this->parser->parseNodes($node->stmts, $this->createBlock()), - $this->parser->parseAttributeGroups($node->attrGroups), + $this->parser->parseAttributeGroups(...$node->attrGroups), $this->mapAttributes($node), )); $this->parser->currentClass = $old; diff --git a/lib/PHPCfg/ParserHandler/Stmt/Property.php b/lib/PHPCfg/ParserHandler/Stmt/Property.php index 9fe7e3a..7a3b688 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/Property.php +++ b/lib/PHPCfg/ParserHandler/Stmt/Property.php @@ -38,7 +38,7 @@ public function handleStmt(Stmt $node): void $visibility, (bool) $static, (bool) $readonly, - $this->parser->parseAttributeGroups($node->attrGroups), + $this->parser->parseAttributeGroups(...$node->attrGroups), $this->parser->parseTypeNode($node->type), $defaultVar, $defaultBlock, diff --git a/lib/PHPCfg/ParserHandler/Stmt/Trait_.php b/lib/PHPCfg/ParserHandler/Stmt/Trait_.php index d2b8aba..774dd6d 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/Trait_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/Trait_.php @@ -23,7 +23,7 @@ public function handleStmt(Stmt $node): void $this->addOp(new Op\Stmt\Trait_( $name, $this->parser->parseNodes($node->stmts, $this->createBlock()), - $this->parser->parseAttributeGroups($node->attrGroups), + $this->parser->parseAttributeGroups(...$node->attrGroups), $this->mapAttributes($node), )); $this->parser->currentClass = $old; From c0eee68a018bac6011250e90f2534b19d7845769 Mon Sep 17 00:00:00 2001 From: Anthony Ferrara Date: Sat, 30 Aug 2025 08:53:20 -0400 Subject: [PATCH 09/14] More iteration, some refactoring --- lib/PHPCfg/Op/Expr/BinaryOp/Pipe.php | 16 +++ lib/PHPCfg/Op/Expr/Cast/Void_.php | 16 +++ lib/PHPCfg/Op/Expr/YieldFrom.php | 33 +++++ lib/PHPCfg/Parser.php | 47 ++++---- lib/PHPCfg/ParserHandler.php | 15 --- lib/PHPCfg/ParserHandler/Batch.php | 16 +++ .../ParserHandler/Batch/AssignAndForeach.php | 113 ++++++++++++++++++ lib/PHPCfg/ParserHandler/Batch/AssignOp.php | 15 +-- lib/PHPCfg/ParserHandler/Batch/BinaryOp.php | 16 +-- lib/PHPCfg/ParserHandler/Batch/IncDec.php | 15 +-- lib/PHPCfg/ParserHandler/Batch/Nop.php | 12 +- lib/PHPCfg/ParserHandler/Batch/Scalar.php | 12 +- lib/PHPCfg/ParserHandler/Batch/Unary.php | 19 +-- lib/PHPCfg/ParserHandler/Expr.php | 19 +++ .../ParserHandler/Expr/ArrayDimFetch.php | 7 +- lib/PHPCfg/ParserHandler/Expr/Array_.php | 7 +- .../ParserHandler/Expr/ArrowFunction.php | 6 +- lib/PHPCfg/ParserHandler/Expr/Assign.php | 66 ---------- lib/PHPCfg/ParserHandler/Expr/AssignRef.php | 7 +- .../ParserHandler/Expr/ClassConstFetch.php | 7 +- lib/PHPCfg/ParserHandler/Expr/Clone_.php | 5 +- lib/PHPCfg/ParserHandler/Expr/Closure_.php | 7 +- lib/PHPCfg/ParserHandler/Expr/ConstFetch.php | 6 +- .../ParserHandler/Expr/ErrorSuppress.php | 7 +- lib/PHPCfg/ParserHandler/Expr/Exit_.php | 7 +- lib/PHPCfg/ParserHandler/Expr/FuncCall.php | 6 +- lib/PHPCfg/ParserHandler/Expr/Include_.php | 7 +- lib/PHPCfg/ParserHandler/Expr/InstanceOf_.php | 7 +- lib/PHPCfg/ParserHandler/Expr/Isset_.php | 7 +- lib/PHPCfg/ParserHandler/Expr/MethodCall.php | 7 +- lib/PHPCfg/ParserHandler/Expr/New_.php | 6 +- .../ParserHandler/Expr/PropertyFetch.php | 7 +- lib/PHPCfg/ParserHandler/Expr/ShellExec.php | 7 +- lib/PHPCfg/ParserHandler/Expr/StaticCall.php | 7 +- .../Expr/StaticPropertyFetch.php | 7 +- lib/PHPCfg/ParserHandler/Expr/Ternary.php | 7 +- lib/PHPCfg/ParserHandler/Expr/Throw_.php | 7 +- lib/PHPCfg/ParserHandler/Expr/Variable.php | 7 +- lib/PHPCfg/ParserHandler/Expr/YieldFrom.php | 27 +++++ lib/PHPCfg/ParserHandler/Expr/Yield_.php | 7 +- lib/PHPCfg/ParserHandler/Stmt.php | 18 +++ lib/PHPCfg/ParserHandler/Stmt/Block.php | 7 +- lib/PHPCfg/ParserHandler/Stmt/ClassConst.php | 7 +- lib/PHPCfg/ParserHandler/Stmt/ClassMethod.php | 7 +- lib/PHPCfg/ParserHandler/Stmt/Class_.php | 7 +- lib/PHPCfg/ParserHandler/Stmt/Const_.php | 7 +- lib/PHPCfg/ParserHandler/Stmt/Declare_.php | 10 +- lib/PHPCfg/ParserHandler/Stmt/Do_.php | 7 +- lib/PHPCfg/ParserHandler/Stmt/Echo_.php | 7 +- lib/PHPCfg/ParserHandler/Stmt/Expression.php | 7 +- lib/PHPCfg/ParserHandler/Stmt/For_.php | 7 +- lib/PHPCfg/ParserHandler/Stmt/Foreach_.php | 59 --------- lib/PHPCfg/ParserHandler/Stmt/Function_.php | 7 +- lib/PHPCfg/ParserHandler/Stmt/Global_.php | 7 +- lib/PHPCfg/ParserHandler/Stmt/Goto_.php | 7 +- .../ParserHandler/Stmt/HaltCompiler.php | 7 +- lib/PHPCfg/ParserHandler/Stmt/If_.php | 11 +- lib/PHPCfg/ParserHandler/Stmt/InlineHTML.php | 7 +- lib/PHPCfg/ParserHandler/Stmt/Interface_.php | 7 +- lib/PHPCfg/ParserHandler/Stmt/Label.php | 7 +- lib/PHPCfg/ParserHandler/Stmt/Namespace_.php | 7 +- lib/PHPCfg/ParserHandler/Stmt/Property.php | 7 +- lib/PHPCfg/ParserHandler/Stmt/Return_.php | 7 +- lib/PHPCfg/ParserHandler/Stmt/Static_.php | 7 +- lib/PHPCfg/ParserHandler/Stmt/Switch_.php | 10 +- lib/PHPCfg/ParserHandler/Stmt/TraitUse.php | 11 +- lib/PHPCfg/ParserHandler/Stmt/Trait_.php | 7 +- lib/PHPCfg/ParserHandler/Stmt/TryCatch.php | 7 +- lib/PHPCfg/ParserHandler/Stmt/Unset_.php | 7 +- lib/PHPCfg/ParserHandler/Stmt/While_.php | 7 +- test/code/yeild_from.test | 80 +++++++++++++ 71 files changed, 627 insertions(+), 359 deletions(-) create mode 100644 lib/PHPCfg/Op/Expr/BinaryOp/Pipe.php create mode 100644 lib/PHPCfg/Op/Expr/Cast/Void_.php create mode 100644 lib/PHPCfg/Op/Expr/YieldFrom.php create mode 100644 lib/PHPCfg/ParserHandler/Batch.php create mode 100644 lib/PHPCfg/ParserHandler/Batch/AssignAndForeach.php create mode 100644 lib/PHPCfg/ParserHandler/Expr.php delete mode 100644 lib/PHPCfg/ParserHandler/Expr/Assign.php create mode 100644 lib/PHPCfg/ParserHandler/Expr/YieldFrom.php create mode 100644 lib/PHPCfg/ParserHandler/Stmt.php delete mode 100644 lib/PHPCfg/ParserHandler/Stmt/Foreach_.php create mode 100644 test/code/yeild_from.test 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/Parser.php b/lib/PHPCfg/Parser.php index e525fc8..5ef3aaf 100755 --- a/lib/PHPCfg/Parser.php +++ b/lib/PHPCfg/Parser.php @@ -51,8 +51,9 @@ class Parser public $anonId = 0; - protected array $handlers = []; - protected array $batchHandlers = []; + protected array $stmtHandlers = []; + protected array $exprHandlers = []; + protected array $opHandlers = []; public function __construct(AstParser $astParser, ?AstTraverser $astTraverser = null) { @@ -69,10 +70,20 @@ public function __construct(AstParser $astParser, ?AstTraverser $astTraverser = public function addHandler(string $name, ParserHandler $handler): void { - if ($handler->isBatch()) { - $this->batchHandlers[$name] = $handler; - } else { - $this->handlers[$name] = $handler; + 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; } } @@ -92,6 +103,11 @@ protected function loadHandlers(): void $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); } @@ -192,16 +208,10 @@ public function parseNode(Node $node): void } $type = $node->getType(); - if (isset($this->handlers[$type])) { - $this->handlers[$type]->handleStmt($node); + if (isset($this->stmtHandlers[$type])) { + $this->stmtHandlers[$type]->handleStmt($node); return; } - foreach ($this->batchHandlers as $handler) { - if ($handler->supports($node)) { - $handler->handleStmt($node); - return; - } - } throw new RuntimeException('Unknown Node Encountered : ' . $type); } @@ -293,13 +303,8 @@ public function parseExprNode($expr): ?Operand return new Literal($expr->value); } - if (isset($this->handlers[$expr->getType()])) { - return $this->handlers[$expr->getType()]->handleExpr($expr); - } - foreach ($this->batchHandlers as $handler) { - if ($handler->supports($expr)) { - return $handler->handleExpr($expr); - } + if (isset($this->exprHandlers[$expr->getType()])) { + return $this->exprHandlers[$expr->getType()]->handleExpr($expr); } throw new RuntimeException('Unknown Expr Type ' . $expr->getType()); } diff --git a/lib/PHPCfg/ParserHandler.php b/lib/PHPCfg/ParserHandler.php index 342843f..1f03f5c 100644 --- a/lib/PHPCfg/ParserHandler.php +++ b/lib/PHPCfg/ParserHandler.php @@ -11,7 +11,6 @@ namespace PHPCfg; -use LogicException; use PhpParser\Node; abstract class ParserHandler @@ -23,20 +22,6 @@ public function __construct(Parser $parser) $this->parser = $parser; } - public function handleExpr(Node\Expr $expr): Operand - { - throw new LogicException("Expr " . $expr->getType() . " not Implemented Yet"); - } - public function handleStmt(Node\Stmt $stmt): void - { - throw new LogicException("Stmt " . $stmt->getType() . " not Implemented Yet"); - } - - public function isBatch(): bool - { - return false; - } - public function getName(): string { $name = str_replace([__CLASS__ . '\\', '_'], '', get_class($this)); 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 index 6c9ea72..55198ea 100644 --- a/lib/PHPCfg/ParserHandler/Batch/AssignOp.php +++ b/lib/PHPCfg/ParserHandler/Batch/AssignOp.php @@ -12,11 +12,12 @@ use PHPCfg\Op; use PHPCfg\Operand; use PHPCfg\ParserHandler; +use PHPCfg\ParserHandler\Batch; +use PHPCfg\ParserHandler\Expr; use PhpParser\Node; -use PhpParser\Node\Expr; use RuntimeException; -class AssignOp extends ParserHandler +class AssignOp extends ParserHandler implements Expr, Batch { private const MAP = [ 'Expr_AssignOp_BitwiseAnd' => Op\Expr\BinaryOp\BitwiseAnd::class, @@ -34,17 +35,17 @@ class AssignOp extends ParserHandler 'Expr_AssignOp_ShiftRight' => Op\Expr\BinaryOp\ShiftRight::class, ]; - public function isBatch(): bool + public function getExprSupport(): array { - return true; + return array_keys(self::MAP); } - public function supports(Node $expr): bool + public function getStmtSupport(): array { - return isset(self::MAP[$expr->getType()]); + return []; } - public function handleExpr(Expr $expr): Operand + public function handleExpr(Node\Expr $expr): Operand { $type = $expr->getType(); if (!isset(self::MAP[$type])) { diff --git a/lib/PHPCfg/ParserHandler/Batch/BinaryOp.php b/lib/PHPCfg/ParserHandler/Batch/BinaryOp.php index bccdc30..646e4d6 100644 --- a/lib/PHPCfg/ParserHandler/Batch/BinaryOp.php +++ b/lib/PHPCfg/ParserHandler/Batch/BinaryOp.php @@ -13,11 +13,12 @@ use PHPCfg\Op; use PHPCfg\Operand; use PHPCfg\ParserHandler; +use PHPCfg\ParserHandler\Batch; +use PHPCfg\ParserHandler\Expr; use PhpParser\Node; -use PhpParser\Node\Expr; use PhpParser\Node\Expr\BinaryOp as AstBinaryOp; -class BinaryOp extends ParserHandler +class BinaryOp extends ParserHandler implements Expr, Batch { private const MAP = [ 'Expr_BinaryOp_LogicalAnd' => '', @@ -40,6 +41,7 @@ class BinaryOp extends ParserHandler '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, @@ -49,17 +51,17 @@ class BinaryOp extends ParserHandler 'Expr_BinaryOp_Spaceship' => Op\Expr\BinaryOp\Spaceship::class, ]; - public function isBatch(): bool + public function getExprSupport(): array { - return true; + return array_keys(self::MAP); } - public function supports(Node $expr): bool + public function getStmtSupport(): array { - return isset(self::MAP[$expr->getType()]); + return []; } - public function handleExpr(Expr $expr): Operand + public function handleExpr(Node\Expr $expr): Operand { $type = $expr->getType(); if (!isset(self::MAP[$type])) { diff --git a/lib/PHPCfg/ParserHandler/Batch/IncDec.php b/lib/PHPCfg/ParserHandler/Batch/IncDec.php index e7035f9..6421830 100644 --- a/lib/PHPCfg/ParserHandler/Batch/IncDec.php +++ b/lib/PHPCfg/ParserHandler/Batch/IncDec.php @@ -12,11 +12,12 @@ use PHPCfg\Op; use PHPCfg\Operand; use PHPCfg\ParserHandler; +use PHPCfg\ParserHandler\Batch; +use PHPCfg\ParserHandler\Expr; use PhpParser\Node; -use PhpParser\Node\Expr; use RuntimeException; -class IncDec extends ParserHandler +class IncDec extends ParserHandler implements Expr, Batch { private const MAP = [ 'Expr_PostDec' => Op\Expr\BinaryOp\Minus::class, @@ -25,17 +26,17 @@ class IncDec extends ParserHandler 'Expr_PreInc' => Op\Expr\BinaryOp\Plus::class, ]; - public function isBatch(): bool + public function getExprSupport(): array { - return true; + return array_keys(self::MAP); } - public function supports(Node $expr): bool + public function getStmtSupport(): array { - return isset(self::MAP[$expr->getType()]); + return []; } - public function handleExpr(Expr $expr): Operand + public function handleExpr(Node\Expr $expr): Operand { $type = $expr->getType(); if (!isset(self::MAP[$type])) { diff --git a/lib/PHPCfg/ParserHandler/Batch/Nop.php b/lib/PHPCfg/ParserHandler/Batch/Nop.php index 681c80c..4c3059d 100644 --- a/lib/PHPCfg/ParserHandler/Batch/Nop.php +++ b/lib/PHPCfg/ParserHandler/Batch/Nop.php @@ -10,9 +10,11 @@ namespace PHPCfg\ParserHandler\Batch; use PHPCfg\ParserHandler; +use PHPCfg\ParserHandler\Batch; +use PHPCfg\ParserHandler\Stmt; use PhpParser\Node; -class Nop extends ParserHandler +class Nop extends ParserHandler implements Batch, Stmt { private const MAP = [ // ignore use statements, since names are already resolved @@ -21,14 +23,14 @@ class Nop extends ParserHandler 'Stmt_Use' => true, ]; - public function isBatch(): bool + public function getExprSupport(): array { - return true; + return []; } - public function supports(Node $expr): bool + public function getStmtSupport(): array { - return isset(self::MAP[$expr->getType()]); + 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 index 438f4e4..696c946 100644 --- a/lib/PHPCfg/ParserHandler/Batch/Scalar.php +++ b/lib/PHPCfg/ParserHandler/Batch/Scalar.php @@ -13,10 +13,12 @@ use PHPCfg\Operand; use PHPCfg\Parser; use PHPCfg\ParserHandler; +use PHPCfg\ParserHandler\Batch; +use PHPCfg\ParserHandler\Expr; use PhpParser\Node; use RuntimeException; -class Scalar extends ParserHandler +class Scalar extends ParserHandler implements Expr, Batch { private const MAP = [ 'Scalar_Encapsed' => true, @@ -27,14 +29,14 @@ class Scalar extends ParserHandler 'Scalar_String' => true, ]; - public function isBatch(): bool + public function getExprSupport(): array { - return true; + return array_keys(self::MAP); } - public function supports(Node $expr): bool + public function getStmtSupport(): array { - return isset(self::MAP[$expr->getType()]) || strpos($expr->getType(), 'Scalar_MagicConst_') === 0; + return []; } public function handleExpr(Node\Expr $scalar): Operand diff --git a/lib/PHPCfg/ParserHandler/Batch/Unary.php b/lib/PHPCfg/ParserHandler/Batch/Unary.php index 8e96ae9..921c09e 100644 --- a/lib/PHPCfg/ParserHandler/Batch/Unary.php +++ b/lib/PHPCfg/ParserHandler/Batch/Unary.php @@ -12,11 +12,12 @@ use PHPCfg\Op; use PHPCfg\Operand; use PHPCfg\ParserHandler; +use PHPCfg\ParserHandler\Batch; +use PHPCfg\ParserHandler\Expr; use PhpParser\Node; -use PhpParser\Node\Expr; use RuntimeException; -class Unary extends ParserHandler +class Unary extends ParserHandler implements Expr, Batch { private const MAP = [ 'Expr_BitwiseNot' => Op\Expr\BitwiseNot::class, @@ -28,6 +29,7 @@ class Unary extends ParserHandler '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, @@ -35,17 +37,18 @@ class Unary extends ParserHandler 'Expr_UnaryPlus' => Op\Expr\UnaryPlus::class, ]; - public function isBatch(): bool + + public function getExprSupport(): array { - return true; + return array_keys(self::MAP); } - public function supports(Node $expr): bool + public function getStmtSupport(): array { - return isset(self::MAP[$expr->getType()]); + return []; } - public function handleExpr(Expr $expr): Operand + public function handleExpr(Node\Expr $expr): Operand { $type = $expr->getType(); if (!isset(self::MAP[$type])) { @@ -57,7 +60,7 @@ public function handleExpr(Expr $expr): Operand $this->mapAttributes($expr) )); - if ($expr instanceof Expr\BooleanNot) { + if ($expr instanceof Node\Expr\BooleanNot) { // process type assertions foreach ($cond->assertions as $assertion) { $result->addAssertion($assertion['var'], new Assertion\NegatedAssertion([$assertion['assertion']])); 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) { diff --git a/lib/PHPCfg/ParserHandler/Expr/Array_.php b/lib/PHPCfg/ParserHandler/Expr/Array_.php index d94d7a6..d313d64 100644 --- a/lib/PHPCfg/ParserHandler/Expr/Array_.php +++ b/lib/PHPCfg/ParserHandler/Expr/Array_.php @@ -12,11 +12,12 @@ use PHPCfg\Op; use PHPCfg\Operand; use PHPCfg\ParserHandler; -use PhpParser\Node\Expr; +use PHPCfg\ParserHandler\Expr; +use PhpParser\Node; -class Array_ extends ParserHandler +class Array_ extends ParserHandler implements Expr { - public function handleExpr(Expr $expr): Operand + public function handleExpr(Node\Expr $expr): Operand { $keys = []; $values = []; diff --git a/lib/PHPCfg/ParserHandler/Expr/ArrowFunction.php b/lib/PHPCfg/ParserHandler/Expr/ArrowFunction.php index 6442098..e65a296 100644 --- a/lib/PHPCfg/ParserHandler/Expr/ArrowFunction.php +++ b/lib/PHPCfg/ParserHandler/Expr/ArrowFunction.php @@ -13,12 +13,12 @@ use PHPCfg\Op; use PHPCfg\Operand; use PHPCfg\ParserHandler; +use PHPCfg\ParserHandler\Expr; use PhpParser\Node; -use PhpParser\Node\Expr; -class ArrowFunction extends ParserHandler +class ArrowFunction extends ParserHandler implements Expr { - public function handleExpr(Expr $expr): Operand + public function handleExpr(Node\Expr $expr): Operand { $flags = Func::FLAG_CLOSURE; $flags |= $expr->byRef ? Func::FLAG_RETURNS_REF : 0; diff --git a/lib/PHPCfg/ParserHandler/Expr/Assign.php b/lib/PHPCfg/ParserHandler/Expr/Assign.php deleted file mode 100644 index f539c48..0000000 --- a/lib/PHPCfg/ParserHandler/Expr/Assign.php +++ /dev/null @@ -1,66 +0,0 @@ -parser->readVariable($this->parser->parseExprNode($expr->expr)); - if ($expr->var instanceof Expr\List_ || $expr->var instanceof Expr\Array_) { - self::parseListAssignment($expr->var, $e, $this->parser, $this->mapAttributes($expr->var)); - - return $e; - } - $v = $this->parser->writeVariable($this->parser->parseExprNode($expr->var)); - - return $this->addExpr(new Op\Expr\Assign($v, $e, $this->mapAttributes($expr))); - } - - /** - * @param Expr\List_|Expr\Array_ $expr - */ - public static function parseListAssignment(Expr $expr, Operand $rhs, Parser $parser, array $attributes): void - { - foreach ($expr->items as $i => $item) { - if (null === $item) { - continue; - } - - if ($item->key === null) { - $key = new Operand\Literal($i); - } else { - $key = $parser->readVariable($parser->parseExprNode($item->key)); - } - - $var = $item->value; - $fetch = new Op\Expr\ArrayDimFetch($rhs, $key, $attributes); - $parser->block->children[] = $fetch; - if ($var instanceof Expr\List_ || $var instanceof Expr\Array_) { - self::parseListAssignment($var, $fetch->result, $parser, $attributes); - - continue; - } - - $assign = new Op\Expr\Assign( - $parser->writeVariable($parser->parseExprNode($var)), - $fetch->result, - $attributes, - ); - $parser->block->children[] = $assign; - } - } -} diff --git a/lib/PHPCfg/ParserHandler/Expr/AssignRef.php b/lib/PHPCfg/ParserHandler/Expr/AssignRef.php index a24fd0e..11b1e7d 100644 --- a/lib/PHPCfg/ParserHandler/Expr/AssignRef.php +++ b/lib/PHPCfg/ParserHandler/Expr/AssignRef.php @@ -12,11 +12,12 @@ use PHPCfg\Op; use PHPCfg\Operand; use PHPCfg\ParserHandler; -use PhpParser\Node\Expr; +use PHPCfg\ParserHandler\Expr; +use PhpParser\Node; -class AssignRef extends ParserHandler +class AssignRef extends ParserHandler implements Expr { - public function handleExpr(Expr $expr): Operand + public function handleExpr(Node\Expr $expr): Operand { $e = $this->parser->readVariable($this->parser->parseExprNode($expr->expr)); $v = $this->parser->writeVariable($this->parser->parseExprNode($expr->var)); diff --git a/lib/PHPCfg/ParserHandler/Expr/ClassConstFetch.php b/lib/PHPCfg/ParserHandler/Expr/ClassConstFetch.php index 4bcaebc..1ad7342 100644 --- a/lib/PHPCfg/ParserHandler/Expr/ClassConstFetch.php +++ b/lib/PHPCfg/ParserHandler/Expr/ClassConstFetch.php @@ -12,11 +12,12 @@ use PHPCfg\Op; use PHPCfg\Operand; use PHPCfg\ParserHandler; -use PhpParser\Node\Expr; +use PHPCfg\ParserHandler\Expr; +use PhpParser\Node; -class ClassConstFetch extends ParserHandler +class ClassConstFetch extends ParserHandler implements Expr { - public function handleExpr(Expr $expr): Operand + public function handleExpr(Node\Expr $expr): Operand { return $this->addExpr(new Op\Expr\ClassConstFetch( diff --git a/lib/PHPCfg/ParserHandler/Expr/Clone_.php b/lib/PHPCfg/ParserHandler/Expr/Clone_.php index 1e2ca31..409522e 100644 --- a/lib/PHPCfg/ParserHandler/Expr/Clone_.php +++ b/lib/PHPCfg/ParserHandler/Expr/Clone_.php @@ -12,11 +12,12 @@ use PHPCfg\Op; use PHPCfg\Operand; use PHPCfg\ParserHandler; +use PHPCfg\ParserHandler\Expr; use PhpParser\Node; -class Clone_ extends ParserHandler +class Clone_ extends ParserHandler implements Expr { - public function handle(Node $expr): Operand + public function handleExpr(Node\Expr $expr): Operand { return $this->addExpr(new Op\Expr\Clone_( $this->parser->readVariable($this->parser->parseExprNode($expr->expr)), diff --git a/lib/PHPCfg/ParserHandler/Expr/Closure_.php b/lib/PHPCfg/ParserHandler/Expr/Closure_.php index b46b645..9d446c5 100644 --- a/lib/PHPCfg/ParserHandler/Expr/Closure_.php +++ b/lib/PHPCfg/ParserHandler/Expr/Closure_.php @@ -13,11 +13,12 @@ use PHPCfg\Op; use PHPCfg\Operand; use PHPCfg\ParserHandler; -use PhpParser\Node\Expr; +use PHPCfg\ParserHandler\Expr; +use PhpParser\Node; -class Closure_ extends ParserHandler +class Closure_ extends ParserHandler implements Expr { - public function handleExpr(Expr $expr): Operand + public function handleExpr(Node\Expr $expr): Operand { $uses = []; foreach ($expr->uses as $use) { diff --git a/lib/PHPCfg/ParserHandler/Expr/ConstFetch.php b/lib/PHPCfg/ParserHandler/Expr/ConstFetch.php index 2733606..395f2a0 100644 --- a/lib/PHPCfg/ParserHandler/Expr/ConstFetch.php +++ b/lib/PHPCfg/ParserHandler/Expr/ConstFetch.php @@ -12,12 +12,12 @@ use PHPCfg\Op; use PHPCfg\Operand; use PHPCfg\ParserHandler; +use PHPCfg\ParserHandler\Expr; use PhpParser\Node; -use PhpParser\Node\Expr; -class ConstFetch extends ParserHandler +class ConstFetch extends ParserHandler implements Expr { - public function handleExpr(Expr $expr): Operand + public function handleExpr(Node\Expr $expr): Operand { if ($expr->name->isUnqualified()) { $lcname = strtolower($expr->name->toString()); diff --git a/lib/PHPCfg/ParserHandler/Expr/ErrorSuppress.php b/lib/PHPCfg/ParserHandler/Expr/ErrorSuppress.php index ea36a7d..aa3c768 100644 --- a/lib/PHPCfg/ParserHandler/Expr/ErrorSuppress.php +++ b/lib/PHPCfg/ParserHandler/Expr/ErrorSuppress.php @@ -13,11 +13,12 @@ use PHPCfg\Op; use PHPCfg\Operand; use PHPCfg\ParserHandler; -use PhpParser\Node\Expr; +use PHPCfg\ParserHandler\Expr; +use PhpParser\Node; -class ErrorSuppress extends ParserHandler +class ErrorSuppress extends ParserHandler implements Expr { - public function handleExpr(Expr $expr): Operand + public function handleExpr(Node\Expr $expr): Operand { $attrs = $this->mapAttributes($expr); $block = new ErrorSuppressBlock(); diff --git a/lib/PHPCfg/ParserHandler/Expr/Exit_.php b/lib/PHPCfg/ParserHandler/Expr/Exit_.php index 1f0e03f..216b7fd 100644 --- a/lib/PHPCfg/ParserHandler/Expr/Exit_.php +++ b/lib/PHPCfg/ParserHandler/Expr/Exit_.php @@ -12,11 +12,12 @@ use PHPCfg\Op; use PHPCfg\Operand; use PHPCfg\ParserHandler; -use PhpParser\Node\Expr; +use PHPCfg\ParserHandler\Expr; +use PhpParser\Node; -class Exit_ extends ParserHandler +class Exit_ extends ParserHandler implements Expr { - public function handleExpr(Expr $expr): Operand + public function handleExpr(Node\Expr $expr): Operand { $e = null; if ($expr->expr) { diff --git a/lib/PHPCfg/ParserHandler/Expr/FuncCall.php b/lib/PHPCfg/ParserHandler/Expr/FuncCall.php index eab2673..362e739 100644 --- a/lib/PHPCfg/ParserHandler/Expr/FuncCall.php +++ b/lib/PHPCfg/ParserHandler/Expr/FuncCall.php @@ -14,12 +14,12 @@ use PHPCfg\Operand; use PHPCfg\Parser; use PHPCfg\ParserHandler; +use PHPCfg\ParserHandler\Expr; use PhpParser\Node; -use PhpParser\Node\Expr; -class FuncCall extends ParserHandler +class FuncCall extends ParserHandler implements Expr { - public function handleExpr(Expr $expr): Operand + public function handleExpr(Node\Expr $expr): Operand { $args = $this->parser->parseExprList($expr->args, Parser::MODE_READ); $name = $this->parser->readVariable($this->parser->parseExprNode($expr->name)); diff --git a/lib/PHPCfg/ParserHandler/Expr/Include_.php b/lib/PHPCfg/ParserHandler/Expr/Include_.php index 3b4a891..70a2db4 100644 --- a/lib/PHPCfg/ParserHandler/Expr/Include_.php +++ b/lib/PHPCfg/ParserHandler/Expr/Include_.php @@ -12,11 +12,12 @@ use PHPCfg\Op; use PHPCfg\Operand; use PHPCfg\ParserHandler; -use PhpParser\Node\Expr; +use PHPCfg\ParserHandler\Expr; +use PhpParser\Node; -class Include_ extends ParserHandler +class Include_ extends ParserHandler implements Expr { - public function handleExpr(Expr $expr): Operand + public function handleExpr(Node\Expr $expr): Operand { return $this->addExpr(new Op\Expr\Include_( $this->parser->readVariable($this->parser->parseExprNode($expr->expr)), diff --git a/lib/PHPCfg/ParserHandler/Expr/InstanceOf_.php b/lib/PHPCfg/ParserHandler/Expr/InstanceOf_.php index b91c052..dad53d8 100644 --- a/lib/PHPCfg/ParserHandler/Expr/InstanceOf_.php +++ b/lib/PHPCfg/ParserHandler/Expr/InstanceOf_.php @@ -12,11 +12,12 @@ use PHPCfg\Op; use PHPCfg\Operand; use PHPCfg\ParserHandler; -use PhpParser\Node\Expr; +use PHPCfg\ParserHandler\Expr; +use PhpParser\Node; -class InstanceOf_ extends ParserHandler +class InstanceOf_ extends ParserHandler implements Expr { - public function handleExpr(Expr $expr): Operand + public function handleExpr(Node\Expr $expr): Operand { $var = $this->parser->readVariable($this->parser->parseExprNode($expr->expr)); $class = $this->parser->readVariable($this->parser->parseExprNode($expr->class)); diff --git a/lib/PHPCfg/ParserHandler/Expr/Isset_.php b/lib/PHPCfg/ParserHandler/Expr/Isset_.php index 38c98fa..9cdb871 100644 --- a/lib/PHPCfg/ParserHandler/Expr/Isset_.php +++ b/lib/PHPCfg/ParserHandler/Expr/Isset_.php @@ -13,11 +13,12 @@ use PHPCfg\Operand; use PHPCfg\Parser; use PHPCfg\ParserHandler; -use PhpParser\Node\Expr; +use PHPCfg\ParserHandler\Expr; +use PhpParser\Node; -class Isset_ extends ParserHandler +class Isset_ extends ParserHandler implements Expr { - public function handleExpr(Expr $expr): Operand + public function handleExpr(Node\Expr $expr): Operand { return $this->addExpr(new Op\Expr\Isset_( $this->parser->parseExprList($expr->vars, Parser::MODE_READ), diff --git a/lib/PHPCfg/ParserHandler/Expr/MethodCall.php b/lib/PHPCfg/ParserHandler/Expr/MethodCall.php index 33e55a7..b8848ee 100644 --- a/lib/PHPCfg/ParserHandler/Expr/MethodCall.php +++ b/lib/PHPCfg/ParserHandler/Expr/MethodCall.php @@ -13,11 +13,12 @@ use PHPCfg\Operand; use PHPCfg\Parser; use PHPCfg\ParserHandler; -use PhpParser\Node\Expr; +use PHPCfg\ParserHandler\Expr; +use PhpParser\Node; -class MethodCall extends ParserHandler +class MethodCall extends ParserHandler implements Expr { - public function handleExpr(Expr $expr): Operand + public function handleExpr(Node\Expr $expr): Operand { return $this->addExpr(new Op\Expr\MethodCall( $this->parser->readVariable($this->parser->parseExprNode($expr->var)), diff --git a/lib/PHPCfg/ParserHandler/Expr/New_.php b/lib/PHPCfg/ParserHandler/Expr/New_.php index 7f6bc2a..3af05d1 100644 --- a/lib/PHPCfg/ParserHandler/Expr/New_.php +++ b/lib/PHPCfg/ParserHandler/Expr/New_.php @@ -13,12 +13,12 @@ use PHPCfg\Operand; use PHPCfg\Parser; use PHPCfg\ParserHandler; +use PHPCfg\ParserHandler\Expr; use PhpParser\Node; -use PhpParser\Node\Expr; -class New_ extends ParserHandler +class New_ extends ParserHandler implements Expr { - public function handleExpr(Expr $expr): Operand + public function handleExpr(Node\Expr $expr): Operand { if ($expr->class instanceof Node\Stmt\Class_) { $this->parser->parseNode($expr->class); diff --git a/lib/PHPCfg/ParserHandler/Expr/PropertyFetch.php b/lib/PHPCfg/ParserHandler/Expr/PropertyFetch.php index 7f1c224..c783972 100644 --- a/lib/PHPCfg/ParserHandler/Expr/PropertyFetch.php +++ b/lib/PHPCfg/ParserHandler/Expr/PropertyFetch.php @@ -12,11 +12,12 @@ use PHPCfg\Op; use PHPCfg\Operand; use PHPCfg\ParserHandler; -use PhpParser\Node\Expr; +use PHPCfg\ParserHandler\Expr; +use PhpParser\Node; -class PropertyFetch extends ParserHandler +class PropertyFetch extends ParserHandler implements Expr { - public function handleExpr(Expr $expr): Operand + public function handleExpr(Node\Expr $expr): Operand { return $this->addExpr(new Op\Expr\PropertyFetch( $this->parser->readVariable($this->parser->parseExprNode($expr->var)), diff --git a/lib/PHPCfg/ParserHandler/Expr/ShellExec.php b/lib/PHPCfg/ParserHandler/Expr/ShellExec.php index 408b614..786b826 100644 --- a/lib/PHPCfg/ParserHandler/Expr/ShellExec.php +++ b/lib/PHPCfg/ParserHandler/Expr/ShellExec.php @@ -13,11 +13,12 @@ use PHPCfg\Operand; use PHPCfg\Parser; use PHPCfg\ParserHandler; -use PhpParser\Node\Expr; +use PHPCfg\ParserHandler\Expr; +use PhpParser\Node; -class ShellExec extends ParserHandler +class ShellExec extends ParserHandler implements Expr { - public function handleExpr(Expr $expr): Operand + public function handleExpr(Node\Expr $expr): Operand { $result = $this->addExpr(new Op\Expr\ConcatList( $this->parser->parseExprList($expr->parts, Parser::MODE_READ), diff --git a/lib/PHPCfg/ParserHandler/Expr/StaticCall.php b/lib/PHPCfg/ParserHandler/Expr/StaticCall.php index 2aece2d..09009ae 100644 --- a/lib/PHPCfg/ParserHandler/Expr/StaticCall.php +++ b/lib/PHPCfg/ParserHandler/Expr/StaticCall.php @@ -13,11 +13,12 @@ use PHPCfg\Operand; use PHPCfg\Parser; use PHPCfg\ParserHandler; -use PhpParser\Node\Expr; +use PHPCfg\ParserHandler\Expr; +use PhpParser\Node; -class StaticCall extends ParserHandler +class StaticCall extends ParserHandler implements Expr { - public function handleExpr(Expr $expr): Operand + public function handleExpr(Node\Expr $expr): Operand { return $this->addExpr(new Op\Expr\StaticCall( $this->parser->readVariable($this->parser->parseExprNode($expr->class)), diff --git a/lib/PHPCfg/ParserHandler/Expr/StaticPropertyFetch.php b/lib/PHPCfg/ParserHandler/Expr/StaticPropertyFetch.php index fbc836b..4ee0615 100644 --- a/lib/PHPCfg/ParserHandler/Expr/StaticPropertyFetch.php +++ b/lib/PHPCfg/ParserHandler/Expr/StaticPropertyFetch.php @@ -12,11 +12,12 @@ use PHPCfg\Op; use PHPCfg\Operand; use PHPCfg\ParserHandler; -use PhpParser\Node\Expr; +use PHPCfg\ParserHandler\Expr; +use PhpParser\Node; -class StaticPropertyFetch extends ParserHandler +class StaticPropertyFetch extends ParserHandler implements Expr { - public function handleExpr(Expr $expr): Operand + public function handleExpr(Node\Expr $expr): Operand { return $this->addExpr(new Op\Expr\StaticPropertyFetch( $this->parser->readVariable($this->parser->parseExprNode($expr->class)), diff --git a/lib/PHPCfg/ParserHandler/Expr/Ternary.php b/lib/PHPCfg/ParserHandler/Expr/Ternary.php index 9fbe4b1..6bdf0ee 100644 --- a/lib/PHPCfg/ParserHandler/Expr/Ternary.php +++ b/lib/PHPCfg/ParserHandler/Expr/Ternary.php @@ -12,11 +12,12 @@ use PHPCfg\Op; use PHPCfg\Operand; use PHPCfg\ParserHandler; -use PhpParser\Node\Expr; +use PHPCfg\ParserHandler\Expr; +use PhpParser\Node; -class Ternary extends ParserHandler +class Ternary extends ParserHandler implements Expr { - public function handleExpr(Expr $expr): Operand + public function handleExpr(Node\Expr $expr): Operand { $attrs = $this->mapAttributes($expr); $cond = $this->parser->readVariable($this->parser->parseExprNode($expr->cond)); diff --git a/lib/PHPCfg/ParserHandler/Expr/Throw_.php b/lib/PHPCfg/ParserHandler/Expr/Throw_.php index d7090bc..3872ab2 100644 --- a/lib/PHPCfg/ParserHandler/Expr/Throw_.php +++ b/lib/PHPCfg/ParserHandler/Expr/Throw_.php @@ -12,11 +12,12 @@ use PHPCfg\Op; use PHPCfg\Operand; use PHPCfg\ParserHandler; -use PhpParser\Node\Expr; +use PHPCfg\ParserHandler\Expr; +use PhpParser\Node; -class Throw_ extends ParserHandler +class Throw_ extends ParserHandler implements Expr { - public function handleExpr(Expr $expr): Operand + public function handleExpr(Node\Expr $expr): Operand { $this->addOp(new Op\Terminal\Throw_( $this->parser->readVariable($this->parser->parseExprNode($expr->expr)), diff --git a/lib/PHPCfg/ParserHandler/Expr/Variable.php b/lib/PHPCfg/ParserHandler/Expr/Variable.php index 3f7d433..0417713 100644 --- a/lib/PHPCfg/ParserHandler/Expr/Variable.php +++ b/lib/PHPCfg/ParserHandler/Expr/Variable.php @@ -12,11 +12,12 @@ use PHPCfg\Op; use PHPCfg\Operand; use PHPCfg\ParserHandler; -use PhpParser\Node\Expr; +use PHPCfg\ParserHandler\Expr; +use PhpParser\Node; -class Variable extends ParserHandler +class Variable extends ParserHandler implements Expr { - public function handleExpr(Expr $expr): Operand + public function handleExpr(Node\Expr $expr): Operand { if (is_scalar($expr->name)) { if ($expr->name === 'this') { 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 index 6adca46..49f4688 100644 --- a/lib/PHPCfg/ParserHandler/Expr/Yield_.php +++ b/lib/PHPCfg/ParserHandler/Expr/Yield_.php @@ -12,11 +12,12 @@ use PHPCfg\Op; use PHPCfg\Operand; use PHPCfg\ParserHandler; -use PhpParser\Node\Expr; +use PHPCfg\ParserHandler\Expr; +use PhpParser\Node; -class Yield_ extends ParserHandler +class Yield_ extends ParserHandler implements Expr { - public function handleExpr(Expr $expr): Operand + public function handleExpr(Node\Expr $expr): Operand { $key = null; $value = null; 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 index 56db213..0b337d5 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/ClassConst.php +++ b/lib/PHPCfg/ParserHandler/Stmt/ClassConst.php @@ -11,12 +11,13 @@ use PHPCfg\Op; use PHPCfg\ParserHandler; -use PhpParser\Node\Stmt; +use PHPCfg\ParserHandler\Stmt; +use PhpParser\Node; use RuntimeException; -class ClassConst extends ParserHandler +class ClassConst extends ParserHandler implements Stmt { - public function handleStmt(Stmt $node): void + public function handleStmt(Node\Stmt $node): void { if (! $this->parser->currentClass instanceof Op\Type\Literal) { throw new RuntimeException('Unknown current class'); diff --git a/lib/PHPCfg/ParserHandler/Stmt/ClassMethod.php b/lib/PHPCfg/ParserHandler/Stmt/ClassMethod.php index 3615111..0747ee8 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/ClassMethod.php +++ b/lib/PHPCfg/ParserHandler/Stmt/ClassMethod.php @@ -12,13 +12,14 @@ use PHPCfg\Func; use PHPCfg\Op; use PHPCfg\ParserHandler; +use PHPCfg\ParserHandler\Stmt; use PhpParser\Modifiers; -use PhpParser\Node\Stmt; +use PhpParser\Node; use RuntimeException; -class ClassMethod extends ParserHandler +class ClassMethod extends ParserHandler implements Stmt { - public function handleStmt(Stmt $node): void + public function handleStmt(Node\Stmt $node): void { if (! $this->parser->currentClass instanceof Op\Type\Literal) { throw new RuntimeException('Unknown current class'); diff --git a/lib/PHPCfg/ParserHandler/Stmt/Class_.php b/lib/PHPCfg/ParserHandler/Stmt/Class_.php index 9752e5b..6028a9d 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/Class_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/Class_.php @@ -11,11 +11,12 @@ use PHPCfg\Op; use PHPCfg\ParserHandler; -use PhpParser\Node\Stmt; +use PHPCfg\ParserHandler\Stmt; +use PhpParser\Node; -class Class_ extends ParserHandler +class Class_ extends ParserHandler implements Stmt { - public function handleStmt(Stmt $node): void + public function handleStmt(Node\Stmt $node): void { $name = $this->parser->parseTypeNode($node->namespacedName); $old = $this->parser->currentClass; diff --git a/lib/PHPCfg/ParserHandler/Stmt/Const_.php b/lib/PHPCfg/ParserHandler/Stmt/Const_.php index ca81b0e..31e18ea 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/Const_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/Const_.php @@ -11,11 +11,12 @@ use PHPCfg\Op; use PHPCfg\ParserHandler; -use PhpParser\Node\Stmt; +use PHPCfg\ParserHandler\Stmt; +use PhpParser\Node; -class Const_ extends ParserHandler +class Const_ extends ParserHandler implements Stmt { - public function handleStmt(Stmt $node): void + public function handleStmt(Node\Stmt $node): void { foreach ($node->consts as $const) { $tmp = $this->block(); diff --git a/lib/PHPCfg/ParserHandler/Stmt/Declare_.php b/lib/PHPCfg/ParserHandler/Stmt/Declare_.php index 339c448..996215b 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/Declare_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/Declare_.php @@ -10,5 +10,13 @@ namespace PHPCfg\ParserHandler\Stmt; use PHPCfg\ParserHandler; +use PHPCfg\ParserHandler\Stmt; +use PhpParser\Node; -class Declare_ extends ParserHandler {} +class Declare_ extends ParserHandler implements Stmt +{ + public function handleStmt(Node\Stmt $stmt): void + { + throw new LogicException("Stmt " . $stmt->getType() . " not Implemented Yet"); + } +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/Do_.php b/lib/PHPCfg/ParserHandler/Stmt/Do_.php index 25bf858..0de0469 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/Do_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/Do_.php @@ -11,11 +11,12 @@ use PHPCfg\Op; use PHPCfg\ParserHandler; -use PhpParser\Node\Stmt; +use PHPCfg\ParserHandler\Stmt; +use PhpParser\Node; -class Do_ extends ParserHandler +class Do_ extends ParserHandler implements Stmt { - public function handleStmt(Stmt $node): void + public function handleStmt(Node\Stmt $node): void { $loopBody = $this->createBlockWithCatchTarget(); $loopEnd = $this->createBlockWithCatchTarget(); diff --git a/lib/PHPCfg/ParserHandler/Stmt/Echo_.php b/lib/PHPCfg/ParserHandler/Stmt/Echo_.php index 21bfa01..d0d319a 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/Echo_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/Echo_.php @@ -11,11 +11,12 @@ use PHPCfg\Op; use PHPCfg\ParserHandler; -use PhpParser\Node\Stmt; +use PHPCfg\ParserHandler\Stmt; +use PhpParser\Node; -class Echo_ extends ParserHandler +class Echo_ extends ParserHandler implements Stmt { - public function handleStmt(Stmt $node): void + public function handleStmt(Node\Stmt $node): void { foreach ($node->exprs as $expr) { $this->addOp(new Op\Terminal\Echo_( diff --git a/lib/PHPCfg/ParserHandler/Stmt/Expression.php b/lib/PHPCfg/ParserHandler/Stmt/Expression.php index cb77870..b4a7d44 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/Expression.php +++ b/lib/PHPCfg/ParserHandler/Stmt/Expression.php @@ -10,11 +10,12 @@ namespace PHPCfg\ParserHandler\Stmt; use PHPCfg\ParserHandler; -use PhpParser\Node\Stmt; +use PHPCfg\ParserHandler\Stmt; +use PhpParser\Node; -class Expression extends ParserHandler +class Expression extends ParserHandler implements Stmt { - public function handleStmt(Stmt $node): void + public function handleStmt(Node\Stmt $node): void { $this->parser->parseExprNode($node->expr); } diff --git a/lib/PHPCfg/ParserHandler/Stmt/For_.php b/lib/PHPCfg/ParserHandler/Stmt/For_.php index ea0ebea..b9b5629 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/For_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/For_.php @@ -13,11 +13,12 @@ use PHPCfg\Operand; use PHPCfg\Parser; use PHPCfg\ParserHandler; -use PhpParser\Node\Stmt; +use PHPCfg\ParserHandler\Stmt; +use PhpParser\Node; -class For_ extends ParserHandler +class For_ extends ParserHandler implements Stmt { - public function handleStmt(Stmt $node): void + public function handleStmt(Node\Stmt $node): void { $this->parser->parseExprList($node->init, Parser::MODE_READ); diff --git a/lib/PHPCfg/ParserHandler/Stmt/Foreach_.php b/lib/PHPCfg/ParserHandler/Stmt/Foreach_.php deleted file mode 100644 index f97fff4..0000000 --- a/lib/PHPCfg/ParserHandler/Stmt/Foreach_.php +++ /dev/null @@ -1,59 +0,0 @@ -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 Expr\List_ || $node->valueVar instanceof Expr\Array_) { - Assign::parseListAssignment($node->valueVar, $valueOp->result, $this->parser, $this->mapAttributes($node->valueVar)); - } 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); - } - -} diff --git a/lib/PHPCfg/ParserHandler/Stmt/Function_.php b/lib/PHPCfg/ParserHandler/Stmt/Function_.php index e5576a6..c8979eb 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/Function_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/Function_.php @@ -12,11 +12,12 @@ use PHPCfg\Func; use PHPCfg\Op; use PHPCfg\ParserHandler; -use PhpParser\Node\Stmt; +use PHPCfg\ParserHandler\Stmt; +use PhpParser\Node; -class Function_ extends ParserHandler +class Function_ extends ParserHandler implements Stmt { - public function handleStmt(Stmt $node): void + public function handleStmt(Node\Stmt $node): void { $this->parser->script->functions[] = $func = new Func( $node->namespacedName->toString(), diff --git a/lib/PHPCfg/ParserHandler/Stmt/Global_.php b/lib/PHPCfg/ParserHandler/Stmt/Global_.php index 4b63644..36f1fe1 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/Global_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/Global_.php @@ -11,11 +11,12 @@ use PHPCfg\Op; use PHPCfg\ParserHandler; -use PhpParser\Node\Stmt; +use PHPCfg\ParserHandler\Stmt; +use PhpParser\Node; -class Global_ extends ParserHandler +class Global_ extends ParserHandler implements Stmt { - public function handleStmt(Stmt $node): void + public function handleStmt(Node\Stmt $node): void { foreach ($node->vars as $var) { // TODO $var is not necessarily a Variable node diff --git a/lib/PHPCfg/ParserHandler/Stmt/Goto_.php b/lib/PHPCfg/ParserHandler/Stmt/Goto_.php index 066af49..866b66d 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/Goto_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/Goto_.php @@ -11,11 +11,12 @@ use PHPCfg\Op; use PHPCfg\ParserHandler; -use PhpParser\Node\Stmt; +use PHPCfg\ParserHandler\Stmt; +use PhpParser\Node; -class Goto_ extends ParserHandler +class Goto_ extends ParserHandler implements Stmt { - public function handleStmt(Stmt $node): void + public function handleStmt(Node\Stmt $node): void { $attributes = $this->mapAttributes($node); if (isset($this->parser->ctx->labels[$node->name->toString()])) { diff --git a/lib/PHPCfg/ParserHandler/Stmt/HaltCompiler.php b/lib/PHPCfg/ParserHandler/Stmt/HaltCompiler.php index 9067658..0cbb53b 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/HaltCompiler.php +++ b/lib/PHPCfg/ParserHandler/Stmt/HaltCompiler.php @@ -12,11 +12,12 @@ use PHPCfg\Op; use PHPCfg\Operand; use PHPCfg\ParserHandler; -use PhpParser\Node\Stmt; +use PHPCfg\ParserHandler\Stmt; +use PhpParser\Node; -class HaltCompiler extends ParserHandler +class HaltCompiler extends ParserHandler implements Stmt { - public function handleStmt(Stmt $node): void + public function handleStmt(Node\Stmt $node): void { $this->addOp(new Op\Terminal\Echo_( $this->parser->readVariable(new Operand\Literal($node->remaining)), diff --git a/lib/PHPCfg/ParserHandler/Stmt/If_.php b/lib/PHPCfg/ParserHandler/Stmt/If_.php index 8c7ba85..f9ff6d8 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/If_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/If_.php @@ -12,18 +12,19 @@ use PHPCfg\Block; use PHPCfg\Op; use PHPCfg\ParserHandler; -use PhpParser\Node\Stmt; +use PHPCfg\ParserHandler\Stmt; +use PhpParser\Node; -class If_ extends ParserHandler +class If_ extends ParserHandler implements Stmt { - public function handleStmt(Stmt $node): void + public function handleStmt(Node\Stmt $node): void { $endBlock = $this->createBlockWithCatchTarget(); $this->parseIf($node, $endBlock); $this->block($endBlock); } - protected function parseIf(Stmt $node, Block $endBlock): void + protected function parseIf(Node\Stmt $node, Block $endBlock): void { $attrs = $this->mapAttributes($node); $cond = $this->parser->readVariable($this->parser->parseExprNode($node->cond)); @@ -38,7 +39,7 @@ protected function parseIf(Stmt $node, Block $endBlock): void $this->block($elseBlock); - if ($node instanceof Stmt\If_) { + if ($node instanceof Node\Stmt\If_) { foreach ($node->elseifs as $elseIf) { $this->parseIf($elseIf, $endBlock); } diff --git a/lib/PHPCfg/ParserHandler/Stmt/InlineHTML.php b/lib/PHPCfg/ParserHandler/Stmt/InlineHTML.php index 9f6d2d5..ec8d827 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/InlineHTML.php +++ b/lib/PHPCfg/ParserHandler/Stmt/InlineHTML.php @@ -11,11 +11,12 @@ use PHPCfg\Op; use PHPCfg\ParserHandler; -use PhpParser\Node\Stmt; +use PHPCfg\ParserHandler\Stmt; +use PhpParser\Node; -class InlineHTML extends ParserHandler +class InlineHTML extends ParserHandler implements Stmt { - public function handleStmt(Stmt $node): void + public function handleStmt(Node\Stmt $node): void { $this->addOp(new Op\Terminal\Echo_( $this->parser->readVariable($this->parser->parseExprNode($node->value)), diff --git a/lib/PHPCfg/ParserHandler/Stmt/Interface_.php b/lib/PHPCfg/ParserHandler/Stmt/Interface_.php index 96c967e..3fdcc32 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/Interface_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/Interface_.php @@ -11,11 +11,12 @@ use PHPCfg\Op; use PHPCfg\ParserHandler; -use PhpParser\Node\Stmt; +use PHPCfg\ParserHandler\Stmt; +use PhpParser\Node; -class Interface_ extends ParserHandler +class Interface_ extends ParserHandler implements Stmt { - public function handleStmt(Stmt $node): void + public function handleStmt(Node\Stmt $node): void { $name = $this->parser->parseTypeNode($node->namespacedName); diff --git a/lib/PHPCfg/ParserHandler/Stmt/Label.php b/lib/PHPCfg/ParserHandler/Stmt/Label.php index dc41c92..25eca40 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/Label.php +++ b/lib/PHPCfg/ParserHandler/Stmt/Label.php @@ -12,12 +12,13 @@ use PHPCfg\Block; use PHPCfg\Op; use PHPCfg\ParserHandler; -use PhpParser\Node\Stmt; +use PHPCfg\ParserHandler\Stmt; +use PhpParser\Node; use RuntimeException; -class Label extends ParserHandler +class Label extends ParserHandler implements Stmt { - public function handleStmt(Stmt $node): void + public function handleStmt(Node\Stmt $node): void { if (isset($this->parser->ctx->labels[$node->name->toString()])) { throw new RuntimeException("Label '{$node->name->toString()}' already defined"); diff --git a/lib/PHPCfg/ParserHandler/Stmt/Namespace_.php b/lib/PHPCfg/ParserHandler/Stmt/Namespace_.php index 1b45cff..b3dd7f0 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/Namespace_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/Namespace_.php @@ -10,11 +10,12 @@ namespace PHPCfg\ParserHandler\Stmt; use PHPCfg\ParserHandler; -use PhpParser\Node\Stmt; +use PHPCfg\ParserHandler\Stmt; +use PhpParser\Node; -class Namespace_ extends ParserHandler +class Namespace_ extends ParserHandler implements Stmt { - public function handleStmt(Stmt $node): void + public function handleStmt(Node\Stmt $node): void { $this->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 index 7a3b688..57a29c8 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/Property.php +++ b/lib/PHPCfg/ParserHandler/Stmt/Property.php @@ -11,12 +11,13 @@ use PHPCfg\Op; use PHPCfg\ParserHandler; +use PHPCfg\ParserHandler\Stmt; use PhpParser\Modifiers; -use PhpParser\Node\Stmt; +use PhpParser\Node; -class Property extends ParserHandler +class Property extends ParserHandler implements Stmt { - public function handleStmt(Stmt $node): void + public function handleStmt(Node\Stmt $node): void { $visibility = $node->flags & Modifiers::VISIBILITY_MASK; $static = $node->flags & Modifiers::STATIC; diff --git a/lib/PHPCfg/ParserHandler/Stmt/Return_.php b/lib/PHPCfg/ParserHandler/Stmt/Return_.php index e6a573e..1a7cb6f 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/Return_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/Return_.php @@ -11,11 +11,12 @@ use PHPCfg\Op; use PHPCfg\ParserHandler; -use PhpParser\Node\Stmt; +use PHPCfg\ParserHandler\Stmt; +use PhpParser\Node; -class Return_ extends ParserHandler +class Return_ extends ParserHandler implements Stmt { - public function handleStmt(Stmt $node): void + public function handleStmt(Node\Stmt $node): void { $expr = null; if ($node->expr) { diff --git a/lib/PHPCfg/ParserHandler/Stmt/Static_.php b/lib/PHPCfg/ParserHandler/Stmt/Static_.php index 60a5ae8..1be1386 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/Static_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/Static_.php @@ -12,11 +12,12 @@ use PHPCfg\Op; use PHPCfg\Operand; use PHPCfg\ParserHandler; -use PhpParser\Node\Stmt; +use PHPCfg\ParserHandler\Stmt; +use PhpParser\Node; -class Static_ extends ParserHandler +class Static_ extends ParserHandler implements Stmt { - public function handleStmt(Stmt $node): void + public function handleStmt(Node\Stmt $node): void { foreach ($node->vars as $var) { $defaultBlock = null; diff --git a/lib/PHPCfg/ParserHandler/Stmt/Switch_.php b/lib/PHPCfg/ParserHandler/Stmt/Switch_.php index 0e7b562..6a417d8 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/Switch_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/Switch_.php @@ -11,12 +11,12 @@ use PHPCfg\Op; use PHPCfg\ParserHandler; +use PHPCfg\ParserHandler\Stmt; use PhpParser\Node; -use PhpParser\Node\Stmt; -class Switch_ extends ParserHandler +class Switch_ extends ParserHandler implements Stmt { - public function handleStmt(Stmt $node): void + public function handleStmt(Node\Stmt $node): void { if ($this->switchCanUseJumptable($node)) { $this->compileJumptableSwitch($node); @@ -65,7 +65,7 @@ public function handleStmt(Stmt $node): void } - private function switchCanUseJumptable(Stmt\Switch_ $node): bool + private function switchCanUseJumptable(Node\Stmt\Switch_ $node): bool { foreach ($node->cases as $case) { if ( @@ -81,7 +81,7 @@ private function switchCanUseJumptable(Stmt\Switch_ $node): bool } - private function compileJumptableSwitch(Stmt\Switch_ $node): void + private function compileJumptableSwitch(Node\Stmt\Switch_ $node): void { $cond = $this->parser->readVariable($this->parser->parseExprNode($node->cond)); $cases = []; diff --git a/lib/PHPCfg/ParserHandler/Stmt/TraitUse.php b/lib/PHPCfg/ParserHandler/Stmt/TraitUse.php index 9636f2c..bef7e1c 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/TraitUse.php +++ b/lib/PHPCfg/ParserHandler/Stmt/TraitUse.php @@ -12,11 +12,12 @@ use PHPCfg\Op; use PHPCfg\Operand; use PHPCfg\ParserHandler; -use PhpParser\Node\Stmt; +use PHPCfg\ParserHandler\Stmt; +use PhpParser\Node; -class TraitUse extends ParserHandler +class TraitUse extends ParserHandler implements Stmt { - public function handleStmt(Stmt $node): void + public function handleStmt(Node\Stmt $node): void { $traits = []; $adaptations = []; @@ -24,7 +25,7 @@ public function handleStmt(Stmt $node): void $traits[] = new Operand\Literal($trait_->toCodeString()); } foreach ($node->adaptations as $adaptation) { - if ($adaptation instanceof Stmt\TraitUseAdaptation\Alias) { + 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), @@ -32,7 +33,7 @@ public function handleStmt(Stmt $node): void $adaptation->newModifier, $this->mapAttributes($adaptation), ); - } elseif ($adaptation instanceof Stmt\TraitUseAdaptation\Precedence) { + } elseif ($adaptation instanceof Node\Stmt\TraitUseAdaptation\Precedence) { $insteadofs = []; foreach ($adaptation->insteadof as $insteadof) { $insteadofs[] = new Operand\Literal($insteadof->toCodeString()); diff --git a/lib/PHPCfg/ParserHandler/Stmt/Trait_.php b/lib/PHPCfg/ParserHandler/Stmt/Trait_.php index 774dd6d..535603b 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/Trait_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/Trait_.php @@ -11,11 +11,12 @@ use PHPCfg\Op; use PHPCfg\ParserHandler; -use PhpParser\Node\Stmt; +use PHPCfg\ParserHandler\Stmt; +use PhpParser\Node; -class Trait_ extends ParserHandler +class Trait_ extends ParserHandler implements Stmt { - public function handleStmt(Stmt $node): void + public function handleStmt(Node\Stmt $node): void { $name = $this->parser->parseTypeNode($node->namespacedName); $old = $this->parser->currentClass; diff --git a/lib/PHPCfg/ParserHandler/Stmt/TryCatch.php b/lib/PHPCfg/ParserHandler/Stmt/TryCatch.php index 7825041..a914d1c 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/TryCatch.php +++ b/lib/PHPCfg/ParserHandler/Stmt/TryCatch.php @@ -14,11 +14,12 @@ use PHPCfg\Op; use PHPCfg\Operand; use PHPCfg\ParserHandler; -use PhpParser\Node\Stmt; +use PHPCfg\ParserHandler\Stmt; +use PhpParser\Node; -class TryCatch extends ParserHandler +class TryCatch extends ParserHandler implements Stmt { - public function handleStmt(Stmt $node): void + public function handleStmt(Node\Stmt $node): void { $finally = new Block(); $catchTarget = new CatchTarget($finally); diff --git a/lib/PHPCfg/ParserHandler/Stmt/Unset_.php b/lib/PHPCfg/ParserHandler/Stmt/Unset_.php index 5f86b8a..496e0c9 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/Unset_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/Unset_.php @@ -12,11 +12,12 @@ use PHPCfg\Op; use PHPCfg\Parser; use PHPCfg\ParserHandler; -use PhpParser\Node\Stmt; +use PHPCfg\ParserHandler\Stmt; +use PhpParser\Node; -class Unset_ extends ParserHandler +class Unset_ extends ParserHandler implements Stmt { - public function handleStmt(Stmt $node): void + public function handleStmt(Node\Stmt $node): void { $this->addOp(new Op\Terminal\Unset_( $this->parser->parseExprList($node->vars, Parser::MODE_WRITE), diff --git a/lib/PHPCfg/ParserHandler/Stmt/While_.php b/lib/PHPCfg/ParserHandler/Stmt/While_.php index fac374e..b8a2e05 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/While_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/While_.php @@ -11,11 +11,12 @@ use PHPCfg\Op; use PHPCfg\ParserHandler; -use PhpParser\Node\Stmt; +use PHPCfg\ParserHandler\Stmt; +use PhpParser\Node; -class While_ extends ParserHandler +class While_ extends ParserHandler implements Stmt { - public function handleStmt(Stmt $node): void + public function handleStmt(Node\Stmt $node): void { $loopInit = $this->createBlockWithCatchTarget(); $loopBody = $this->createBlockWithCatchTarget(); diff --git a/test/code/yeild_from.test b/test/code/yeild_from.test new file mode 100644 index 0000000..67f5b26 --- /dev/null +++ b/test/code/yeild_from.test @@ -0,0 +1,80 @@ + + 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 From 73b8e6e32788d2c65025a0dfa11201be9f55fee0 Mon Sep 17 00:00:00 2001 From: Anthony Ferrara Date: Sat, 30 Aug 2025 11:39:35 -0400 Subject: [PATCH 10/14] Much more work --- lib/PHPCfg/Op/Stmt/MatchTable.php | 46 ++++++++ lib/PHPCfg/Op/Terminal/MatchError.php | 32 +++++ lib/PHPCfg/Op/Type/Intersection.php | 29 +++++ lib/PHPCfg/Parser.php | 16 ++- lib/PHPCfg/ParserHandler.php | 9 +- lib/PHPCfg/ParserHandler/Batch/Scalar.php | 10 +- .../Expr/{InstanceOf_.php => Instanceof_.php} | 3 +- lib/PHPCfg/ParserHandler/Expr/Match_.php | 111 ++++++++++++++++++ lib/PHPCfg/ParserHandler/Stmt/Declare_.php | 17 ++- lib/PHPCfg/Printer.php | 5 +- lib/PHPCfg/Script.php | 8 +- test/code/intersection_type.test | 17 +++ test/code/match.test | 31 +++++ 13 files changed, 311 insertions(+), 23 deletions(-) create mode 100644 lib/PHPCfg/Op/Stmt/MatchTable.php create mode 100644 lib/PHPCfg/Op/Terminal/MatchError.php create mode 100644 lib/PHPCfg/Op/Type/Intersection.php rename lib/PHPCfg/ParserHandler/Expr/{InstanceOf_.php => Instanceof_.php} (91%) create mode 100644 lib/PHPCfg/ParserHandler/Expr/Match_.php create mode 100644 test/code/intersection_type.test create mode 100644 test/code/match.test diff --git a/lib/PHPCfg/Op/Stmt/MatchTable.php b/lib/PHPCfg/Op/Stmt/MatchTable.php new file mode 100644 index 0000000..98f2b2e --- /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/Terminal/MatchError.php b/lib/PHPCfg/Op/Terminal/MatchError.php new file mode 100644 index 0000000..4899e6e --- /dev/null +++ b/lib/PHPCfg/Op/Terminal/MatchError.php @@ -0,0 +1,32 @@ +cond = $this->addReadRef($cond); + } + + public function getVariableNames(): array + { + return ['cond' => $this->cond]; + } +} 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/Parser.php b/lib/PHPCfg/Parser.php index 5ef3aaf..bcf7dac 100755 --- a/lib/PHPCfg/Parser.php +++ b/lib/PHPCfg/Parser.php @@ -244,6 +244,11 @@ public function parseTypeNode(?Node $node): Op\Type $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, @@ -267,7 +272,6 @@ public function parseExprList(array $expr, $readWrite = self::MODE_NONE): array } elseif ($readWrite === self::MODE_WRITE) { $vars = array_map([$this, 'writeVariable'], $vars); } - return $vars; } @@ -306,6 +310,7 @@ public function parseExprNode($expr): ?Operand if (isset($this->exprHandlers[$expr->getType()])) { return $this->exprHandlers[$expr->getType()]->handleExpr($expr); } + var_dump(array_keys($this->exprHandlers)); throw new RuntimeException('Unknown Expr Type ' . $expr->getType()); } @@ -331,12 +336,7 @@ 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); } public function parseParameterList(Func $func, array $params): array @@ -372,8 +372,6 @@ public function parseParameterList(Func $func, array $params): array return $result; } - - public function mapAttributes(Node $expr): array { return array_merge( diff --git a/lib/PHPCfg/ParserHandler.php b/lib/PHPCfg/ParserHandler.php index 1f03f5c..24c30e1 100644 --- a/lib/PHPCfg/ParserHandler.php +++ b/lib/PHPCfg/ParserHandler.php @@ -51,14 +51,9 @@ protected function createBlockWithCatchTarget(): Block return new Block(null, $this->parser->block->catchTarget); } - protected function mapAttributes(Node $expr): array + protected function mapAttributes(Node $node): array { - return array_merge( - [ - 'filename' => $this->parser->fileName, - ], - $expr->getAttributes(), - ); + return $this->parser->mapAttributes($node); } protected function addOp(Op $op): void diff --git a/lib/PHPCfg/ParserHandler/Batch/Scalar.php b/lib/PHPCfg/ParserHandler/Batch/Scalar.php index 696c946..7523ff5 100644 --- a/lib/PHPCfg/ParserHandler/Batch/Scalar.php +++ b/lib/PHPCfg/ParserHandler/Batch/Scalar.php @@ -27,6 +27,12 @@ class Scalar extends ParserHandler implements Expr, Batch '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 @@ -59,9 +65,9 @@ public function handleExpr(Node\Expr $scalar): Operand // TODO return new Operand\Literal('__CLASS__'); case 'Scalar_MagicConst_Dir': - return new Operand\Literal(dirname($this->fileName)); + return new Operand\Literal(dirname($this->parser->fileName)); case 'Scalar_MagicConst_File': - return new Operand\Literal($this->fileName); + return new Operand\Literal($this->parser->fileName); case 'Scalar_MagicConst_Namespace': // TODO return new Operand\Literal('__NAMESPACE__'); diff --git a/lib/PHPCfg/ParserHandler/Expr/InstanceOf_.php b/lib/PHPCfg/ParserHandler/Expr/Instanceof_.php similarity index 91% rename from lib/PHPCfg/ParserHandler/Expr/InstanceOf_.php rename to lib/PHPCfg/ParserHandler/Expr/Instanceof_.php index dad53d8..0775c02 100644 --- a/lib/PHPCfg/ParserHandler/Expr/InstanceOf_.php +++ b/lib/PHPCfg/ParserHandler/Expr/Instanceof_.php @@ -9,13 +9,14 @@ namespace PHPCfg\ParserHandler\Expr; +use PHPCfg\Assertion; use PHPCfg\Op; use PHPCfg\Operand; use PHPCfg\ParserHandler; use PHPCfg\ParserHandler\Expr; use PhpParser\Node; -class InstanceOf_ extends ParserHandler implements Expr +class Instanceof_ extends ParserHandler implements Expr { public function handleExpr(Node\Expr $expr): Operand { diff --git a/lib/PHPCfg/ParserHandler/Expr/Match_.php b/lib/PHPCfg/ParserHandler/Expr/Match_.php new file mode 100644 index 0000000..0405915 --- /dev/null +++ b/lib/PHPCfg/ParserHandler/Expr/Match_.php @@ -0,0 +1,111 @@ +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->createBlockWithCatchTarget()); + $result = $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->createBlockWithCatchTarget()); + $result = $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 list($case, $block, $result)) { + $phi->addOperand($result); + $match->addArm($case, $block); + } + + $this->block($endBlock); + + return $endResult; + } + +} diff --git a/lib/PHPCfg/ParserHandler/Stmt/Declare_.php b/lib/PHPCfg/ParserHandler/Stmt/Declare_.php index 996215b..69e4647 100644 --- a/lib/PHPCfg/ParserHandler/Stmt/Declare_.php +++ b/lib/PHPCfg/ParserHandler/Stmt/Declare_.php @@ -9,6 +9,7 @@ namespace PHPCfg\ParserHandler\Stmt; +use LogicException; use PHPCfg\ParserHandler; use PHPCfg\ParserHandler\Stmt; use PhpParser\Node; @@ -17,6 +18,20 @@ class Declare_ extends ParserHandler implements Stmt { public function handleStmt(Node\Stmt $stmt): void { - throw new LogicException("Stmt " . $stmt->getType() . " not Implemented Yet"); + foreach ($stmt->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/Printer.php b/lib/PHPCfg/Printer.php index 6be9bbb..53572fa 100755 --- a/lib/PHPCfg/Printer.php +++ b/lib/PHPCfg/Printer.php @@ -316,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++; } diff --git a/lib/PHPCfg/Script.php b/lib/PHPCfg/Script.php index 095a302..0f3d307 100644 --- a/lib/PHPCfg/Script.php +++ b/lib/PHPCfg/Script.php @@ -16,6 +16,12 @@ class Script /** @var Func[] */ public array $functions = []; - /** @var Func */ public Func $main; + + public bool $strict_types = false; + + public int $ticks = 0; + + public string $encoding = 'ISO-8859-1'; + } 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..6050b41 --- /dev/null +++ b/test/code/match.test @@ -0,0 +1,31 @@ + '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#2 + armBlocks[2]: Block#3 + +Block#2 + Parent: Block#3 + Var#1 = Phi(LITERAL('foo'), LITERAL('bar'), Var#2) + Terminal_Echo + expr: Var#1 + Terminal_Return + +Block#3 + Expr_BinaryOp_Concat + left: LITERAL('baz') + right: LITERAL('buz') + result: Var#2 + Stmt_Jump + target: Block#2 From 6b923a264ee156a42862ab6661f4f934696b7546 Mon Sep 17 00:00:00 2001 From: Anthony Ferrara Date: Sat, 30 Aug 2025 16:39:34 -0400 Subject: [PATCH 11/14] Test other match implementation --- lib/PHPCfg/Op/Stmt/MatchTable.php | 4 +- lib/PHPCfg/Op/Terminal/MatchError.php | 1 - lib/PHPCfg/ParserHandler/Expr/Match_.php | 12 +++-- test/code/match2.test | 65 ++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 8 deletions(-) create mode 100644 test/code/match2.test diff --git a/lib/PHPCfg/Op/Stmt/MatchTable.php b/lib/PHPCfg/Op/Stmt/MatchTable.php index 98f2b2e..79ebbe0 100644 --- a/lib/PHPCfg/Op/Stmt/MatchTable.php +++ b/lib/PHPCfg/Op/Stmt/MatchTable.php @@ -11,9 +11,9 @@ namespace PHPCfg\Op\Stmt; +use PHPCfg\Block; use PHPCfg\Op\Stmt; use PHPCfg\Operand; -use PHPCfg\Block; class MatchTable extends Stmt { @@ -38,7 +38,7 @@ 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/Terminal/MatchError.php b/lib/PHPCfg/Op/Terminal/MatchError.php index 4899e6e..39d84a0 100644 --- a/lib/PHPCfg/Op/Terminal/MatchError.php +++ b/lib/PHPCfg/Op/Terminal/MatchError.php @@ -16,7 +16,6 @@ class MatchError extends Terminal { - public Operand $cond; public function __construct(Operand $cond, array $attributes = []) diff --git a/lib/PHPCfg/ParserHandler/Expr/Match_.php b/lib/PHPCfg/ParserHandler/Expr/Match_.php index 0405915..24caed8 100644 --- a/lib/PHPCfg/ParserHandler/Expr/Match_.php +++ b/lib/PHPCfg/ParserHandler/Expr/Match_.php @@ -42,7 +42,8 @@ private function canImplementJumpTable(Node\Expr\Match_ $expr): bool return true; } - private function implementNonJumpTable(Node\Expr\Match_ $expr): Operand { + private function implementNonJumpTable(Node\Expr\Match_ $expr): Operand + { $endResult = new Operand\Temporary(); $endBlock = $this->createBlockWithCatchTarget(); $phi = new Op\Phi($endResult, ['block' => $endBlock]); @@ -73,7 +74,8 @@ private function implementNonJumpTable(Node\Expr\Match_ $expr): Operand { return $endResult; } - private function implementJumpTable(Node\Expr\Match_ $expr): Operand { + private function implementJumpTable(Node\Expr\Match_ $expr): Operand + { $endBlock = $this->createBlockWithCatchTarget(); $matchCond = $this->parser->readVariable($this->parser->parseExprNode($expr->cond)); $thisBlock = $this->block(); @@ -98,13 +100,13 @@ private function implementJumpTable(Node\Expr\Match_ $expr): Operand { $endResult = new Operand\Temporary(); $phi = new Op\Phi($endResult, ['block' => $endBlock]); $endBlock->phi[] = $phi; - foreach ($table as list($case, $block, $result)) { + foreach ($table as [$case, $block, $result]) { $phi->addOperand($result); $match->addArm($case, $block); } - + $this->block($endBlock); - + return $endResult; } diff --git a/test/code/match2.test b/test/code/match2.test new file mode 100644 index 0000000..84e7d28 --- /dev/null +++ b/test/code/match2.test @@ -0,0 +1,65 @@ + '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 + Parent: Block#3 + Parent: Block#5 + Var#2 = Phi(LITERAL('foo'), LITERAL('bar'), Var#3) + Terminal_Echo + expr: Var#2 + Terminal_Return + +Block#3 + Parent: Block#1 + Expr_BinaryOp_Identical + left: LITERAL(5) + right: LITERAL(10) + result: Var#4 + Stmt_JumpIf + cond: Var#4 + if: Block#2 + else: Block#4 + +Block#4 + Parent: Block#3 + Expr_BinaryOp_Plus + left: LITERAL(5) + right: LITERAL(5) + result: Var#5 + Expr_BinaryOp_Identical + left: Var#5 + right: LITERAL(10) + result: Var#6 + Stmt_JumpIf + cond: Var#6 + if: Block#5 + else: Block#6 + +Block#5 + Parent: Block#4 + Expr_BinaryOp_Concat + left: LITERAL('baz') + right: LITERAL('buz') + result: Var#3 + Stmt_Jump + target: Block#2 + +Block#6 + Parent: Block#4 + Terminal_MatchError + cond: LITERAL(10) From 5dc34a9da3d091abd197c436bf6ea2d82f892a6d Mon Sep 17 00:00:00 2001 From: Anthony Ferrara Date: Sat, 30 Aug 2025 17:10:58 -0400 Subject: [PATCH 12/14] Switch Match to always generate an assign, even for literals, to ensure that there's a way to trace origin --- demo.php | 2 +- lib/PHPCfg/ParserHandler/Expr/Match_.php | 17 +++++-- test/code/match.test | 41 +++++++++++++---- test/code/match2.test | 58 ++++++++++++++++-------- 4 files changed, 84 insertions(+), 34 deletions(-) diff --git a/demo.php b/demo.php index 4aae789..636e35d 100755 --- a/demo.php +++ b/demo.php @@ -13,7 +13,7 @@ require __DIR__ . '/vendor/autoload.php'; -$graphviz = true; +$graphviz = false; [$fileName, $code] = getCode($argc, $argv); $parser = new PHPCfg\Parser((new ParserFactory())->createForNewestSupportedVersion()); diff --git a/lib/PHPCfg/ParserHandler/Expr/Match_.php b/lib/PHPCfg/ParserHandler/Expr/Match_.php index 24caed8..3ff6e54 100644 --- a/lib/PHPCfg/ParserHandler/Expr/Match_.php +++ b/lib/PHPCfg/ParserHandler/Expr/Match_.php @@ -51,8 +51,8 @@ private function implementNonJumpTable(Node\Expr\Match_ $expr): Operand $matchCond = $this->parser->readVariable($this->parser->parseExprNode($expr->cond)); foreach ($expr->arms as $arm) { $current = $this->block(); - $block = $this->block($this->createBlockWithCatchTarget()); - $result = $this->parser->parseExprNode($arm->body); + $block = $this->block($this->createBlockWithParent()); + $result = $this->ensureTemporary($this->parser->parseExprNode($arm->body)); $phi->addOperand($result); if (empty($block->children)) { $block = $endBlock; @@ -81,8 +81,8 @@ private function implementJumpTable(Node\Expr\Match_ $expr): Operand $thisBlock = $this->block(); $table = []; foreach ($expr->arms as $arm) { - $block = $this->block($this->createBlockWithCatchTarget()); - $result = $this->parser->parseExprNode($arm->body); + $block = $this->block($this->createBlockWithParent()); + $result = $this->ensureTemporary($this->parser->parseExprNode($arm->body)); if (empty($block->children)) { $block = $endBlock; } else { @@ -110,4 +110,13 @@ private function implementJumpTable(Node\Expr\Match_ $expr): Operand 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/test/code/match.test b/test/code/match.test index 6050b41..2d302d2 100644 --- a/test/code/match.test +++ b/test/code/match.test @@ -12,20 +12,43 @@ Block#1 armConditions[1]: LITERAL(5) armConditions[2]: LITERAL(10) armBlocks[0]: Block#2 - armBlocks[1]: Block#2 - armBlocks[2]: Block#3 + armBlocks[1]: Block#3 + armBlocks[2]: Block#4 Block#2 - Parent: Block#3 - Var#1 = Phi(LITERAL('foo'), LITERAL('bar'), Var#2) - Terminal_Echo - expr: Var#1 - Terminal_Return + 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#2 + result: Var#5 Stmt_Jump - target: Block#2 + 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 index 84e7d28..8a8cb8f 100644 --- a/test/code/match2.test +++ b/test/code/match2.test @@ -17,12 +17,12 @@ Block#1 Block#2 Parent: Block#1 - Parent: Block#3 - Parent: Block#5 - Var#2 = Phi(LITERAL('foo'), LITERAL('bar'), Var#3) - Terminal_Echo - expr: Var#2 - Terminal_Return + Expr_Assign + var: Var#2 + expr: LITERAL('foo') + result: Var#3 + Stmt_Jump + target: Block#4 Block#3 Parent: Block#1 @@ -32,34 +32,52 @@ Block#3 result: Var#4 Stmt_JumpIf cond: Var#4 - if: Block#2 - else: Block#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#5 + result: Var#9 Expr_BinaryOp_Identical - left: Var#5 + left: Var#9 right: LITERAL(10) - result: Var#6 + result: Var#10 Stmt_JumpIf - cond: Var#6 - if: Block#5 - else: Block#6 + cond: Var#10 + if: Block#7 + else: Block#8 -Block#5 - Parent: Block#4 +Block#7 + Parent: Block#6 Expr_BinaryOp_Concat left: LITERAL('baz') right: LITERAL('buz') - result: Var#3 + result: Var#7 Stmt_Jump - target: Block#2 + target: Block#4 -Block#6 - Parent: Block#4 +Block#8 + Parent: Block#6 Terminal_MatchError cond: LITERAL(10) From b5254c7c39c06c8850f06b97a42df58e3386db80 Mon Sep 17 00:00:00 2001 From: Anthony Ferrara Date: Sat, 30 Aug 2025 18:06:45 -0400 Subject: [PATCH 13/14] Add cli test runner --- .php-cs-fixer.php | 1 + README.md | 8 +++++ bin/php-cfg | 13 ++++++++ composer.json | 11 +++++-- demo.php | 62 -------------------------------------- src/Cli/BaseCommand.php | 35 +++++++++++++++++++++ src/Cli/DotCommand.php | 53 ++++++++++++++++++++++++++++++++ src/Cli/PrintCommand.php | 46 ++++++++++++++++++++++++++++ src/Cli/RunTestCommand.php | 47 +++++++++++++++++++++++++++++ 9 files changed, 211 insertions(+), 65 deletions(-) create mode 100755 bin/php-cfg delete mode 100755 demo.php create mode 100644 src/Cli/BaseCommand.php create mode 100644 src/Cli/DotCommand.php create mode 100644 src/Cli/PrintCommand.php create mode 100644 src/Cli/RunTestCommand.php diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index 3e17f30..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()) 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 636e35d..0000000 --- a/demo.php +++ /dev/null @@ -1,62 +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], '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); + } +} From b86e2f071992ad00f0129e273165c9a7152196a9 Mon Sep 17 00:00:00 2001 From: Anthony Ferrara Date: Sun, 31 Aug 2025 15:51:47 -0400 Subject: [PATCH 14/14] Move tests back to test --- phpunit.xml.dist | 9 ++------- test/{ => integration}/ParserAttributesTest.php | 0 {lib/PHPCfg => test/unit}/AssertionTest.php | 0 .../PHPCfg => test/unit}/AstVisitor/LoopResolverTest.php | 0 .../unit}/AstVisitor/MagicStringResolverTest.php | 0 .../PHPCfg => test/unit}/AstVisitor/NameResolverTest.php | 0 {lib/PHPCfg => test/unit}/OpTest.php | 0 7 files changed, 2 insertions(+), 7 deletions(-) rename test/{ => integration}/ParserAttributesTest.php (100%) rename {lib/PHPCfg => test/unit}/AssertionTest.php (100%) rename {lib/PHPCfg => test/unit}/AstVisitor/LoopResolverTest.php (100%) rename {lib/PHPCfg => test/unit}/AstVisitor/MagicStringResolverTest.php (100%) rename {lib/PHPCfg => test/unit}/AstVisitor/NameResolverTest.php (100%) rename {lib/PHPCfg => test/unit}/OpTest.php (100%) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 35e96b4..1d2c9ba 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -5,19 +5,14 @@ stopOnFailure="false" bootstrap="./vendor/autoload.php"> - + ./test/ - - ./lib/ - ./lib/ + ./src/ - - ./lib/ - \ No newline at end of file diff --git a/test/ParserAttributesTest.php b/test/integration/ParserAttributesTest.php similarity index 100% rename from test/ParserAttributesTest.php rename to test/integration/ParserAttributesTest.php diff --git a/lib/PHPCfg/AssertionTest.php b/test/unit/AssertionTest.php similarity index 100% rename from lib/PHPCfg/AssertionTest.php rename to test/unit/AssertionTest.php diff --git a/lib/PHPCfg/AstVisitor/LoopResolverTest.php b/test/unit/AstVisitor/LoopResolverTest.php similarity index 100% rename from lib/PHPCfg/AstVisitor/LoopResolverTest.php rename to test/unit/AstVisitor/LoopResolverTest.php diff --git a/lib/PHPCfg/AstVisitor/MagicStringResolverTest.php b/test/unit/AstVisitor/MagicStringResolverTest.php similarity index 100% rename from lib/PHPCfg/AstVisitor/MagicStringResolverTest.php rename to test/unit/AstVisitor/MagicStringResolverTest.php diff --git a/lib/PHPCfg/AstVisitor/NameResolverTest.php b/test/unit/AstVisitor/NameResolverTest.php similarity index 100% rename from lib/PHPCfg/AstVisitor/NameResolverTest.php rename to test/unit/AstVisitor/NameResolverTest.php diff --git a/lib/PHPCfg/OpTest.php b/test/unit/OpTest.php similarity index 100% rename from lib/PHPCfg/OpTest.php rename to test/unit/OpTest.php