Skip to content

Commit

Permalink
Introduce Script & Func structures
Browse files Browse the repository at this point in the history
This means that each function now has its own control flow graph
and own variable space.

Also resolves some state pollution issues between different functions,
e.g. regarding labels.
  • Loading branch information
nikic committed Mar 22, 2016
1 parent fe6731a commit 0d54187
Show file tree
Hide file tree
Showing 18 changed files with 252 additions and 157 deletions.
6 changes: 3 additions & 3 deletions demo.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ function foo(array $a) {
EOF;


$block = $parser->parse($code, __FILE__);
$traverser->traverse($block);
$script = $parser->parse($code, __FILE__);
$traverser->traverse($script);

$dumper = new PHPCfg\Printer\Text();
echo $dumper->printCFG(array($block));
echo $dumper->printScript($script);

34 changes: 34 additions & 0 deletions lib/PHPCfg/Func.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

/*
* This file is part of PHP-CFG, a Control flow graph implementation for PHP
*
* @copyright 2015 Anthony Ferrara. All rights reserved
* @license MIT See LICENSE at the root of the project for more info
*/

namespace PHPCfg;

class Func {
/** @var string */
public $name;
/** @var bool */
public $returnsRef;
/** @var */
public $returnType;
/** @var */
public $class;
/** @var Op\Expr\Param[] */
public $params;
/** @var Block|null */
public $cfg;

public function __construct($name, $returnsRef, $returnType, $class) {
$this->name = $name;
$this->returnsRef = $returnsRef;
$this->returnType = $returnType;
$this->class = $class;
$this->params = [];
$this->cfg = new Block;
}
}
5 changes: 4 additions & 1 deletion lib/PHPCfg/Op/CallableOp.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@

namespace PHPCfg\Op;

use PHPCfg\Func;

interface CallableOp {

public function getParams();
/** @returns Func */
public function getFunc();

}
28 changes: 6 additions & 22 deletions lib/PHPCfg/Op/Expr/Closure.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,22 @@

namespace PHPCfg\Op\Expr;

use PhpCfg\Block;
use PHPCfg\Func;
use PHPCfg\Op\CallableOp;
use PHPCfg\Op\Expr;

class Closure extends Expr implements CallableOp {
public $byRef;

public $params;

public $returnType;

public $stmts;

public $globals;

public $func;
public $useVars;

public function __construct(array $params, array $useVars, $byRef, $returnType, Block $stmts, array $attributes = []) {
public function __construct(Func $func, array $useVars, array $attributes = []) {
parent::__construct($attributes);
$this->params = $this->addReadRef($params);
$this->func = $func;
$this->useVars = $this->addReadRef($useVars);
$this->byRef = (bool) $byRef;
$this->returnType = $returnType;
$this->stmts = $stmts;
}

public function getParams() {
return $this->params;
}

public function getSubBlocks() {
return ['stmts'];
public function getFunc() {
return $this->func;
}

public function getVariableNames() {
Expand Down
9 changes: 0 additions & 9 deletions lib/PHPCfg/Op/Stmt/ClassMethod.php
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,5 @@

namespace PHPCfg\Op\Stmt;

use PHPCfg\Block;
use PHPCfg\Operand;

class ClassMethod extends Function_ {
public $class;

public function __construct(Operand $class, $name, array $params, $byRef, $returnType, Block $stmts = null, array $attributes = []) {
parent::__construct($name, $params, $byRef, $returnType, $stmts, $attributes);
$this->class = $this->addReadRef($class);
}
}
35 changes: 8 additions & 27 deletions lib/PHPCfg/Op/Stmt/Function_.php
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -9,42 +9,23 @@

namespace PHPCfg\Op\Stmt;

use PhpCfg\Block;
use PHPCfg\Func;
use PHPCfg\Op\CallableOp;
use PHPCfg\Op\Stmt;

class Function_ extends Stmt implements CallableOp {
public $byRef;
public $func;

public $name;

public $params;

public $paramVars = [];

public $returnType;

public $stmts;

public $globals;

public function __construct($name, array $params, $byRef, $returnType, Block $stmts = null, array $attributes = []) {
public function __construct(Func $func, array $attributes = []) {
parent::__construct($attributes);
$this->name = $this->addReadRef($name);
$this->params = $params;
foreach ($params as $param) {
$this->paramVars[] = $this->addReadRef($param->result);
}
$this->byRef = (bool) $byRef;
$this->returnType = $returnType;
$this->stmts = $stmts;
$this->func = $func;
}

public function getSubBlocks() {
return ['stmts'];
public function getFunc() {
return $this->func;
}

public function getParams() {
return $this->params;
public function getSubBlocks() {
return [];
}
}
112 changes: 67 additions & 45 deletions lib/PHPCfg/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ class Parser {

/** @var Literal|null */
protected $currentClass = null;

/** @var Script */
protected $script;
protected $anonId = 0;

public function __construct(AstParser $astParser, AstTraverser $astTraverser = null) {
$this->astParser = $astParser;
Expand All @@ -54,14 +56,46 @@ public function __construct(AstParser $astParser, AstTraverser $astTraverser = n
$this->incompletePhis = new \SplObjectStorage;
}

/**
* @param string $code
* @param string $fileName
* @returns Script
*/
public function parse($code, $fileName) {
$this->labels = [];
$this->fileName = $fileName;
$ast = $this->astParser->parse($code);
$ast = $this->astTraverser->traverse($ast);

$this->script = $script = new Script();
$script->functions = [];
$script->main = new Func('{main}', false, null, null);
$this->parseFunc($script->main, [], $ast);

// Reset script specific state
$this->script = null;
$this->currentClass = null;

return $script;
}

protected function parseFunc(Func $func, array $params, array $stmts) {
// Back up function specific structures
$prevScope = $this->scope;
$prevIncompletePhis = $this->incompletePhis;
$prevLabels = $this->labels;
$this->scope = new \SplObjectStorage;
$this->incompletePhis = new \SplObjectStorage;
$this->labels = [];

$start = $func->cfg;

$this->complete = false;
$this->parseNodes($ast, $start = new Block);
$func->params = $this->parseParameterList($func, $params);
foreach ($func->params as $param) {
$this->writeVariableName($param->name->value, $param->result, $start);
}

$this->parseNodes($stmts, $start);
$this->complete = true;

foreach ($this->incompletePhis as $block) {
Expand All @@ -78,8 +112,10 @@ public function parse($code, $fileName) {
$block->phi[] = $phi;
}
}
$this->incompletePhis->removeAllExcept(new \SplObjectStorage);
return $start;

$this->scope = $prevScope;
$this->incompletePhis = $prevIncompletePhis;
$this->labels = $prevLabels;
}

public function parseNodes(array $nodes, Block $block) {
Expand Down Expand Up @@ -143,28 +179,22 @@ protected function parseStmt_ClassMethod(Stmt\ClassMethod $node) {
if (!$this->currentClass instanceof Operand) {
throw new \RuntimeException("Unknown current class");
}
$params = $this->parseParameterList($node->params);
if ($node->stmts) {
$block = new Block;
foreach ($params as $param) {
$this->writeVariableName($param->name->value, $param->result, $block);
}
$this->parseNodes($node->stmts, $block);
} else {
$block = null;
}
$this->block->children[] = $func = new Op\Stmt\ClassMethod(
$this->currentClass,
$this->parseExprNode($node->name),
$params,

$this->script->functions[] = $func = new Func(
$node->name,
$node->byRef,
$this->parseExprNode($node->returnType),
$block,
$this->mapAttributes($node)
$this->currentClass
);
foreach ($params as $param) {
$param->function = $func;

if ($node->stmts) {
$this->parseFunc($func, $node->params, $node->stmts);
} else {
$func->params = $this->parseParameterList($func, $node->params);
$func->cfg = null;
}

$this->block->children[] = new Op\Stmt\ClassMethod($func, $this->mapAttributes($node));
}

protected function parseStmt_Const(Stmt\Const_ $node) {
Expand Down Expand Up @@ -281,23 +311,14 @@ protected function parseStmt_Foreach(Stmt\Foreach_ $node) {
}

protected function parseStmt_Function(Stmt\Function_ $node) {
$block = new Block;
$params = $this->parseParameterList($node->params);
foreach ($params as $param) {
$this->writeVariableName($param->name->value, $param->result, $block);
}
$this->parseNodes($node->stmts, $block);
$this->block->children[] = $func = new Op\Stmt\Function_(
$this->parseExprNode($node->namespacedName),
$params,
$this->script->functions[] = $func = new Func(
$node->namespacedName,
$node->byRef,
$this->parseExprNode($node->returnType),
$block,
$this->mapAttributes($node)
null
);
foreach ($params as $param) {
$param->function = $func;
}
$this->parseFunc($func, $node->params, $node->stmts);
$this->block->children[] = new Op\Stmt\Function_($func, $this->mapAttributes($node));
}

protected function parseStmt_Global(Stmt\Global_ $node) {
Expand Down Expand Up @@ -812,8 +833,6 @@ protected function parseExpr_BooleanNot(Expr\BooleanNot $expr) {
}

protected function parseExpr_Closure(Expr\Closure $expr) {
$block = new Block;
$this->parseNodes($expr->stmts, $block);
$uses = [];
foreach ($expr->uses as $use) {
$uses[] = new Operand\BoundVariable(
Expand All @@ -822,14 +841,16 @@ protected function parseExpr_Closure(Expr\Closure $expr) {
Operand\BoundVariable::SCOPE_LOCAL
);
}
return new Op\Expr\Closure(
$this->parseParameterList($expr->params),
$uses,

$this->script->functions[] = $func = new Func(
'{anonymous}#' . ++$this->anonId,
$expr->byRef,
$this->parseExprNode($expr->returnType),
$block,
$this->mapAttributes($expr)
null
);
$this->parseFunc($func, $expr->params, $expr->stmts);

return new Op\Expr\Closure($func, $uses, $this->mapAttributes($expr));
}

protected function parseExpr_ClassConstFetch(Expr\ClassConstFetch $expr) {
Expand Down Expand Up @@ -1127,7 +1148,7 @@ private function parseScalarNode(Node\Scalar $scalar) {
}
}

private function parseParameterList(array $params) {
private function parseParameterList(Func $func, array $params) {
if (empty($params)) {
return [];
}
Expand All @@ -1152,6 +1173,7 @@ private function parseParameterList(array $params) {
$this->mapAttributes($param)
);
$p->result->original = new Operand\Variable(new Operand\Literal($p->name->value));
$p->function = $func;
}
return $result;
}
Expand Down
Loading

0 comments on commit 0d54187

Please sign in to comment.