Completed
Push — master ( 8f0d0f...15736a )
by Дмитрий
06:53
created

ControlFlowGraph::passExpr()   D

Complexity

Conditions 10
Paths 10

Size

Total Lines 36
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 10.0094

Importance

Changes 0
Metric Value
cc 10
eloc 23
nc 10
nop 1
dl 0
loc 36
ccs 21
cts 22
cp 0.9545
crap 10.0094
rs 4.8196
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @author Patsura Dmitry https://github.com/ovr <[email protected]>
4
 */
5
6
namespace PHPSA\ControlFlow;
7
8
use PhpParser\Node\Stmt\ClassMethod;
9
use PhpParser\Node\Stmt\Function_;
10
use PHPSA\Context;
11
use PHPSA\ControlFlow\Node;
12
13
class ControlFlowGraph
14
{
15
    /**
16
     * @var int
17
     */
18
    protected $lastBlockId = 1;
19
20
    /**
21
     * @var Block
22
     */
23
    protected $root;
24
25
    /**
26
     * @var Block[]
27
     */
28
    protected $labels;
29
30
    /**
31
     * @todo
32
     *
33
     * @var \PhpParser\Node\Stmt\Goto_[]
34
     */
35
    protected $unresolvedGotos;
36
37
    /**
38
     * @var Context
39
     */
40
    protected $context;
41
42
    /**
43
     * @param $statement
44
     */
45 52
    public function __construct($statement, Context $context)
46
    {
47 52
        $this->context = $context;
48 52
        $this->root = new Block($this->lastBlockId++);
49
50 52
        if ($statement instanceof Function_) {
51 6
            if ($statement->stmts) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $statement->stmts of type PhpParser\Node[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
52 5
                $this->passNodes($statement->stmts, $this->root);
53
            }
54
        }
55
56 52
        if ($statement instanceof ClassMethod) {
57 52
            if ($statement->stmts) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $statement->stmts of type PhpParser\Node[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
58 51
                $this->passNodes($statement->stmts, $this->root);
59
            }
60
        }
61 52
    }
62
63
    /**
64
     * @param \PhpParser\Node[] $nodes
65
     * @param Block $block
66
     */
67 51
    protected function passNodes(array $nodes, Block $block)
68
    {
69 51
        foreach ($nodes as $stmt) {
70 51
            switch (get_class($stmt)) {
71 51
                case \PhpParser\Node\Stmt\Goto_::class:
72 1
                    if (isset($this->labels[$stmt->name])) {
0 ignored issues
show
Bug introduced by
Accessing name on the interface PhpParser\Node suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
73 1
                        $block->addChildren(
74 1
                            new Node\JumpNode($this->labels[$stmt->name])
0 ignored issues
show
Bug introduced by
Accessing name on the interface PhpParser\Node suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
75
                        );
76
                    } else {
77
                        $this->unresolvedGotos[] = $stmt;
78
                    }
79 1
                    break;
80 51
                case \PhpParser\Node\Expr\Assign::class:
81 26
                    $this->passAssign($stmt, $block);
0 ignored issues
show
Compatibility introduced by
$stmt of type object<PhpParser\Node> is not a sub-type of object<PhpParser\Node\Expr\Assign>. It seems like you assume a concrete implementation of the interface PhpParser\Node to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
82 26
                    break;
83 48
                case \PhpParser\Node\Stmt\Return_::class:
84 37
                    $this->passReturn($stmt, $block);
0 ignored issues
show
Compatibility introduced by
$stmt of type object<PhpParser\Node> is not a sub-type of object<PhpParser\Node\Stmt\Return_>. It seems like you assume a concrete implementation of the interface PhpParser\Node to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
85 37
                    break;
86 29
                case \PhpParser\Node\Stmt\For_::class:
87 4
                    $block = $this->passFor($stmt, $block);
0 ignored issues
show
Compatibility introduced by
$stmt of type object<PhpParser\Node> is not a sub-type of object<PhpParser\Node\Stmt\For_>. It seems like you assume a concrete implementation of the interface PhpParser\Node to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
88 4
                    break;
89 29
                case \PhpParser\Node\Stmt\If_::class:
90 6
                    $block = $this->passIf($stmt, $block);
0 ignored issues
show
Compatibility introduced by
$stmt of type object<PhpParser\Node> is not a sub-type of object<PhpParser\Node\Stmt\If_>. It seems like you assume a concrete implementation of the interface PhpParser\Node to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
91 6
                    break;
92 27
                case \PhpParser\Node\Stmt\While_::class:
93 3
                    $block = $this->passWhile($stmt, $block);
0 ignored issues
show
Compatibility introduced by
$stmt of type object<PhpParser\Node> is not a sub-type of object<PhpParser\Node\Stmt\While_>. It seems like you assume a concrete implementation of the interface PhpParser\Node to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
94 3
                    break;
95 27
                case \PhpParser\Node\Stmt\Do_::class:
96 3
                    $block = $this->passDo($stmt, $block);
0 ignored issues
show
Compatibility introduced by
$stmt of type object<PhpParser\Node> is not a sub-type of object<PhpParser\Node\Stmt\Do_>. It seems like you assume a concrete implementation of the interface PhpParser\Node to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
97 3
                    break;
98 27
                case \PhpParser\Node\Stmt\Throw_::class:
99 1
                    $this->passThrow($stmt, $block);
0 ignored issues
show
Compatibility introduced by
$stmt of type object<PhpParser\Node> is not a sub-type of object<PhpParser\Node\Stmt\Throw_>. It seems like you assume a concrete implementation of the interface PhpParser\Node to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
100 1
                    break;
101 27
                case \PhpParser\Node\Expr\Exit_::class:
102 1
                    $block->addChildren(new Node\ExitNode());
103 1
                    break;
104 26
                case \PhpParser\Node\Stmt\Label::class:
105 1
                    $block = $this->createNewBlockIfNeeded($block);
106 1
                    $block->label = $stmt->name;
0 ignored issues
show
Bug introduced by
Accessing name on the interface PhpParser\Node suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
107 1
                    $this->labels[$block->label] = $block;
108 1
                    break;
109 25
                case \PhpParser\Node\Stmt\TryCatch::class:
110 2
                    $block = $this->passTryCatch($stmt, $block);
0 ignored issues
show
Compatibility introduced by
$stmt of type object<PhpParser\Node> is not a sub-type of object<PhpParser\Node\Stmt\TryCatch>. It seems like you assume a concrete implementation of the interface PhpParser\Node to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
111 2
                    break;
112 25
                case \PhpParser\Node\Stmt\Nop::class:
113
                    // ignore commented code
114 1
                    break;
115
                default:
116 25
                    $this->context->debug('[CFG] Unimplemented ' . get_class($stmt), $stmt);
117 51
                    break;
118
            }
119
        }
120 51
    }
121
122
    /**
123
     * If current block is not empty, lets create a new one
124
     *
125
     * @param Block $block
126
     * @return Block
127
     */
128 1
    protected function createNewBlockIfNeeded(Block $block)
129
    {
130 1
        if (!$block->getChildren()) {
131 1
            $next = new Block($this->lastBlockId++);
132
133 1
            $next->setExit(
134 1
                $block
135
            );
136
137 1
            return $next;
138
        }
139
140
        return $block;
141
    }
142
143
    /**
144
     * @param \PhpParser\Node\Expr $expr
145
     * @return Node\AbstractNode
146
     */
147 38
    protected function passExpr(\PhpParser\Node\Expr $expr)
148
    {
149 38
        switch (get_class($expr)) {
150 38
            case \PhpParser\Node\Expr\BinaryOp\NotIdentical::class:
151 1
                return new Node\Expr\BinaryOp\NotIdentical();
152
153 38
            case \PhpParser\Node\Expr\BinaryOp\Identical::class:
154 1
                return new Node\Expr\BinaryOp\Identical();
155
156 38
            case \PhpParser\Node\Expr\BinaryOp\NotEqual::class:
157 1
                return new Node\Expr\BinaryOp\NotEqual();
158
159 38
            case \PhpParser\Node\Expr\BinaryOp\Equal::class:
160 4
                return new Node\Expr\BinaryOp\Equal();
161
162 38
            case \PhpParser\Node\Expr\BinaryOp\Smaller::class:
163 1
                return new Node\Expr\BinaryOp\Smaller();
164
165 38
            case \PhpParser\Node\Expr\BinaryOp\SmallerOrEqual::class:
166 1
                return new Node\Expr\BinaryOp\SmallerOrEqual();
167
168 38
            case \PhpParser\Node\Expr\BinaryOp\Greater::class:
169 2
                return new Node\Expr\BinaryOp\Greater();
170
171 38
            case \PhpParser\Node\Expr\BinaryOp\GreaterOrEqual::class:
172 1
                return new Node\Expr\BinaryOp\GreaterOrEqual();
173
174 38
            case \PhpParser\Node\Expr\Instanceof_::class:
175
                return new Node\Expr\InstanceOfExpr();
176
177
            default:
178 38
                $this->context->debug('[CFG] Unimplemented ' . get_class($expr), $expr);
179
        }
180
181 38
        return new Node\UnknownNode();
182
    }
183
184
    /**
185
     * @param \PhpParser\Node\Stmt\If_ $if
186
     * @param Block $block
187
     * @return Block
188
     */
189 6
    protected function passIf(\PhpParser\Node\Stmt\If_ $if, Block $block)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $if. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
190
    {
191 6
        $trueBlock = new Block($this->lastBlockId++);
192 6
        $this->passNodes($if->stmts, $trueBlock);
193
194 6
        $jumpIf = new Node\JumpIfNode($this->passExpr($if->cond), $trueBlock);
195
196 6
        $elseBlock = null;
197
198 6
        if ($if->else) {
199 1
            if ($if->else->stmts) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $if->else->stmts of type PhpParser\Node[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
200 1
                $elseBlock = new Block($this->lastBlockId++);
201 1
                $this->passNodes($if->else->stmts, $elseBlock);
202
203 1
                $jumpIf->setElse($elseBlock);
204
            }
205
        }
206
207 6
        $block->addChildren(
208 6
            $jumpIf
209
        );
210
211 6
        $exitBlock = new Block($this->lastBlockId++);
212 6
        $trueBlock->setExit($exitBlock);
213
214 6
        if ($elseBlock) {
215 1
            $elseBlock->setExit($exitBlock);
216
        }
217
218 6
        return $exitBlock;
219
    }
220
221
    /**
222
     * @param \PhpParser\Node\Stmt\For_ $for
223
     * @param Block $block
224
     * @return Block
225
     */
226 4
    protected function passFor(\PhpParser\Node\Stmt\For_ $for, Block $block)
227
    {
228 4
        $this->passNodes($for->init, $block);
229
230 4
        $block->setExit(
231 4
            $loop = new Block($this->lastBlockId++)
232
        );
233 4
        $this->passNodes($for->stmts, $loop);
234
235 4
        $loop->setExit(
236 4
            $after = new Block($this->lastBlockId++)
237
        );
238 4
        return $after;
239
    }
240
241
    /**
242
     * @param \PhpParser\Node\Stmt\Do_ $do
243
     * @param Block $block
244
     * @return Block
245
     */
246 3
    protected function passDo(\PhpParser\Node\Stmt\Do_ $do, Block $block)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $do. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
247
    {
248 3
        $loop = new Block($this->lastBlockId++);
249 3
        $this->passNodes($do->stmts, $loop);
250
251 3
        $block->setExit($loop);
252
253 3
        $cond = new Block($this->lastBlockId++);
254 3
        $loop->setExit($cond);
255
256 3
        $jumpIf = new Node\JumpIfNode($this->passExpr($do->cond), $loop);
257 3
        $cond->addChildren($jumpIf);
258
259 3
        $exitBlock = new Block($this->lastBlockId++);
260 3
        $jumpIf->setElse($exitBlock);
261
262 3
        return $exitBlock;
263
    }
264
265
    /**
266
     * @param \PhpParser\Node\Stmt\While_ $while
267
     * @param Block $block
268
     * @return Block
269
     */
270 3
    protected function passWhile(\PhpParser\Node\Stmt\While_ $while, Block $block)
271
    {
272 3
        $cond = new Block($this->lastBlockId++);
273 3
        $block->setExit(
274 3
            $cond
275
        );
276
277 3
        $loop = new Block($this->lastBlockId++);
278
279 3
        $jumpIf = new Node\JumpIfNode($this->passExpr($while->cond), $loop);
280 3
        $cond->addChildren($jumpIf);
281
282 3
        $this->passNodes($while->stmts, $loop);
283
284 3
        $loop->addChildren(new Node\JumpNode($cond));
285
        //$loop->setExit($cond);
286
287 3
        $after = new Block($this->lastBlockId++);
288 3
        $jumpIf->setElse($after);
289
290 3
        return $after;
291
    }
292
293
    /**
294
     * @param \PhpParser\Node\Stmt\Throw_ $throw_
295
     * @param Block $block
296
     */
297 1
    protected function passThrow(\PhpParser\Node\Stmt\Throw_ $throw_, Block $block)
0 ignored issues
show
Unused Code introduced by
The parameter $throw_ is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
298
    {
299 1
        $block->addChildren(new Node\ThrowNode());
300 1
    }
301
302
    /**
303
     * @param \PhpParser\Node\Expr\Assign $assign
304
     * @param Block $block
305
     */
306 26
    protected function passAssign(\PhpParser\Node\Expr\Assign $assign, Block $block)
0 ignored issues
show
Unused Code introduced by
The parameter $assign is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
307
    {
308 26
        $block->addChildren(new Node\AssignNode());
309 26
    }
310
311
    /**
312
     * @param \PhpParser\Node\Stmt\Return_ $return
313
     * @param Block $block
314
     */
315 37
    protected function passReturn(\PhpParser\Node\Stmt\Return_ $return, Block $block)
316
    {
317 37
        if ($return->expr) {
318 37
            $block->addChildren(
319 37
                new Node\ReturnNode(
320 37
                    $this->passExpr(
321 37
                        $return->expr
322
                    )
323
                )
324
            );
325
        } else {
326 1
            $block->addChildren(
327 1
                new Node\ReturnNode()
328
            );
329
        }
330 37
    }
331
332
    /**
333
     * @param \PhpParser\Node\Stmt\TryCatch $stmt
334
     * @param Block $block
335
     * @return Block
336
     */
337 2
    protected function passTryCatch(\PhpParser\Node\Stmt\TryCatch $stmt, Block $block)
338
    {
339 2
        $try = new Block($this->lastBlockId++);
340 2
        $this->passNodes($stmt->stmts, $try);
341
342 2
        $block->setExit($try);
343
344 2
        if ($stmt->finally) {
345
            $finally = new Block($this->lastBlockId++);
346
            $this->passNodes($stmt->finally->stmts, $finally);
347
348
            $try->setExit($finally);
349
            return $finally;
350
        }
351
352 2
        return $try;
353
    }
354
355
    /**
356
     * @return Block
357
     */
358
    public function getRoot()
359
    {
360
        return $this->root;
361
    }
362
}
363