Completed
Pull Request — master (#293)
by Дмитрий
03:19
created

ControlFlowGraph::passIf()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 31
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 17
nc 6
nop 2
dl 0
loc 31
ccs 0
cts 20
cp 0
crap 20
rs 8.5806
c 0
b 0
f 0
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\Function_;
9
use PHPSA\ControlFlow\Node;
10
11
class ControlFlowGraph
12
{
13
    /**
14
     * @var int
15
     */
16
    protected $lastBlockId = 1;
17
18
    /**
19
     * @var Block
20
     */
21
    protected $root;
22
23
    /**
24
     * @var Block[]
25
     */
26
    protected $labels;
27
28
    /**
29
     * @todo
30
     *
31
     * @var \PhpParser\Node\Stmt\Goto_[]
32
     */
33
    protected $unresolvedGotos;
34
35
    /**
36
     * @param $statement
37
     */
38 6
    public function __construct($statement)
39
    {
40 6
        $this->root = new Block($this->lastBlockId++);
41
42 6
        if ($statement instanceof Function_) {
43 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...
44 5
                $this->passNodes($statement->stmts, $this->root);
45 5
            }
46 6
        }
47 6
    }
48
49
    /**
50
     * @param array $nodes
51
     * @param Block $block
52
     */
53 5
    protected function passNodes(array $nodes, Block $block)
54
    {
55 5
        foreach ($nodes as $stmt) {
56 5
            switch (get_class($stmt)) {
57 5
                case \PhpParser\Node\Stmt\Goto_::class:
58
                    if (isset($this->labels[$stmt->name])) {
59
                        $block->addChildren(
60
                            new Node\JumpNode($this->labels[$stmt->name])
61
                        );
62
                    } else {
63
                        $this->unresolvedGotos[] = $stmt;
64
                    }
65
                    break;
66 5
                case \PhpParser\Node\Expr\Assign::class:
67
                    $this->passAssign($stmt, $block);
68
                    break;
69 5
                case \PhpParser\Node\Stmt\Return_::class:
70 4
                    $this->passReturn($stmt, $block);
71 4
                    break;
72 1
                case \PhpParser\Node\Stmt\For_::class:
73
                    $block = $this->passFor($stmt, $block);
74
                    break;
75 1
                case \PhpParser\Node\Stmt\If_::class:
76
                    $block = $this->passIf($stmt, $block);
77
                    break;
78 1
                case \PhpParser\Node\Stmt\While_::class:
79
                    $block = $this->passWhile($stmt, $block);
80
                    break;
81 1
                case \PhpParser\Node\Stmt\Do_::class:
82
                    $block = $this->passDo($stmt, $block);
83
                    break;
84 1
                case \PhpParser\Node\Stmt\Throw_::class:
85
                    $this->passThrow($stmt, $block);
86
                    break;
87 1
                case \PhpParser\Node\Expr\Exit_::class:
88
                    $block->addChildren(new Node\ExitNode());
89
                    break;
90 1
                case \PhpParser\Node\Stmt\Label::class:
91
                    $block = $this->createNewBlockIfNeeded($block);
92
                    $block->label = $stmt->name;
93
                    $this->labels[$block->label] = $block;
94
                    break;
95 1
                case \PhpParser\Node\Stmt\TryCatch::class:
96
                    $block = $this->passTryCatch($stmt, $block);
97
                    break;
98 1
                case \PhpParser\Node\Stmt\Nop::class:
99
                    // ignore commented code
100
                    break;
101 1
                default:
102 1
                    echo 'Unimplemented ' . get_class($stmt) . PHP_EOL;
103 1
                    break;
104 5
            }
105 5
        }
106 5
    }
107
108
    /**
109
     * If current block is not empty, lets create a new one
110
     *
111
     * @param Block $block
112
     * @return Block
113
     */
114
    protected function createNewBlockIfNeeded(Block $block)
115
    {
116
        if ($block->getChildren()) {
117
            $block->setExit(
118
                $block = new Block($this->lastBlockId++)
119
            );
120
        }
121
122
        return $block;
123
    }
124
125
    /**
126
     * @param \PhpParser\Node\Expr $expr
127
     * @return Node\AbstractNode
128
     */
129 4
    protected function passExpr(\PhpParser\Node\Expr $expr)
130
    {
131 4
        switch (get_class($expr)) {
132 4
            case \PhpParser\Node\Expr\BinaryOp\NotIdentical::class:
133
                return new Node\Expr\BinaryOp\NotIdentical();
134
135 4
            case \PhpParser\Node\Expr\BinaryOp\Identical::class:
136
                return new Node\Expr\BinaryOp\Identical();
137
138 4
            case \PhpParser\Node\Expr\BinaryOp\NotEqual::class:
139
                return new Node\Expr\BinaryOp\NotEqual();
140
141 4
            case \PhpParser\Node\Expr\BinaryOp\Equal::class:
142
                return new Node\Expr\BinaryOp\Equal();
143
144 4
            case \PhpParser\Node\Expr\BinaryOp\Smaller::class:
145
                return new Node\Expr\BinaryOp\Smaller();
146
147 4
            case \PhpParser\Node\Expr\BinaryOp\SmallerOrEqual::class:
148
                return new Node\Expr\BinaryOp\SmallerOrEqual();
149
150 4
            case \PhpParser\Node\Expr\BinaryOp\Greater::class:
151
                return new Node\Expr\BinaryOp\Greater();
152
153 4
            case \PhpParser\Node\Expr\BinaryOp\GreaterOrEqual::class:
154
                return new Node\Expr\BinaryOp\GreaterOrEqual();
155
156 4
            case \PhpParser\Node\Expr\Instanceof_::class:
157
                return new Node\Expr\InstanceOfExpr();
158
159 4
            default:
160 4
                echo 'Unimplemented ' . get_class($expr) . PHP_EOL;
161 4
        }
162
163 4
        return new Node\UnknownNode();
164
    }
165
166
    /**
167
     * @param \PhpParser\Node\Stmt\If_ $if
168
     * @param Block $block
169
     * @return Block
170
     */
171
    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...
172
    {
173
        $trueBlock = new Block($this->lastBlockId++);
174
        $this->passNodes($if->stmts, $trueBlock);
175
176
        $jumpIf = new Node\JumpIfNode($this->passExpr($if->cond), $trueBlock);
177
178
        $elseBlock = null;
179
180
        if ($if->else) {
181
            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...
182
                $elseBlock = new Block($this->lastBlockId++);
183
                $this->passNodes($if->else->stmts, $elseBlock);
184
185
                $jumpIf->setElse($elseBlock);
186
            }
187
        }
188
189
        $block->addChildren(
190
            $jumpIf
191
        );
192
193
        $exitBlock = new Block($this->lastBlockId++);
194
        $trueBlock->setExit($exitBlock);
195
196
        if ($elseBlock) {
197
            $elseBlock->setExit($exitBlock);
198
        }
199
200
        return $exitBlock;
201
    }
202
203
    /**
204
     * @param \PhpParser\Node\Stmt\For_ $for
205
     * @param Block $block
206
     * @return Block
207
     */
208
    protected function passFor(\PhpParser\Node\Stmt\For_ $for, Block $block)
209
    {
210
        $this->passNodes($for->init, $block);
211
212
        $block->setExit(
213
            $loop = new Block($this->lastBlockId++)
214
        );
215
        $this->passNodes($for->stmts, $loop);
216
217
        $loop->setExit(
218
            $after = new Block($this->lastBlockId++)
219
        );
220
        return $after;
221
    }
222
223
    /**
224
     * @param \PhpParser\Node\Stmt\Do_ $do
225
     * @param Block $block
226
     * @return Block
227
     */
228
    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...
229
    {
230
        $loop = new Block($this->lastBlockId++);
231
        $this->passNodes($do->stmts, $loop);
232
233
        $block->setExit($loop);
234
235
        $cond = new Block($this->lastBlockId++);
236
        $loop->setExit($cond);
237
238
        $jumpIf = new Node\JumpIfNode($this->passExpr($do->cond), $loop);
239
        $cond->addChildren($jumpIf);
240
241
        $exitBlock = new Block($this->lastBlockId++);
242
        $jumpIf->setElse($exitBlock);
243
244
        return $exitBlock;
245
    }
246
247
    /**
248
     * @param \PhpParser\Node\Stmt\While_ $while
249
     * @param Block $block
250
     * @return Block
251
     */
252
    protected function passWhile(\PhpParser\Node\Stmt\While_ $while, Block $block)
253
    {
254
        $cond = new Block($this->lastBlockId++);
255
        $block->setExit(
256
            $cond
257
        );
258
259
        $loop = new Block($this->lastBlockId++);
260
261
        $jumpIf = new Node\JumpIfNode($this->passExpr($while->cond), $loop);
262
        $cond->addChildren($jumpIf);
263
264
        $this->passNodes($while->stmts, $loop);
265
266
        $loop->addChildren(new Node\JumpNode($cond));
267
        //$loop->setExit($cond);
268
269
        $after = new Block($this->lastBlockId++);
270
        $jumpIf->setElse($after);
271
272
        return $after;
273
    }
274
275
    /**
276
     * @param \PhpParser\Node\Stmt\Throw_ $throw_
277
     * @param Block $block
278
     */
279
    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...
280
    {
281
        $block->addChildren(new Node\ThrowNode());
282
    }
283
284
    /**
285
     * @param \PhpParser\Node\Expr\Assign $assign
286
     * @param Block $block
287
     */
288
    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...
289
    {
290
        $block->addChildren(new Node\AssignNode());
291
    }
292
293
    /**
294
     * @param \PhpParser\Node\Stmt\Return_ $return
295
     * @param Block $block
296
     */
297 4
    protected function passReturn(\PhpParser\Node\Stmt\Return_ $return, Block $block)
298
    {
299 4
        if ($return->expr) {
300 4
            $block->addChildren(
301 4
                new Node\ReturnNode(
302 4
                    $this->passExpr(
303 4
                        $return->expr
304 4
                    )
305 4
                )
306 4
            );
307 4
        } else {
308
            $block->addChildren(
309
                new Node\ReturnNode()
310
            );
311
        }
312 4
    }
313
314
    /**
315
     * @param \PhpParser\Node\Stmt\TryCatch $stmt
316
     * @param Block $block
317
     * @return Block
318
     */
319
    protected function passTryCatch(\PhpParser\Node\Stmt\TryCatch $stmt, Block $block)
320
    {
321
        $try = new Block($this->lastBlockId++);
322
        $this->passNodes($stmt->stmts, $try);
323
324
        $block->setExit($try);
325
326
        if ($stmt->finally) {
327
            $finally = new Block($this->lastBlockId++);
328
            $this->passNodes($stmt->finally->stmts, $finally);
329
330
            $try->setExit($finally);
331
            return $finally;
332
        }
333
334
        return $try;
335
    }
336
337
    /**
338
     * @return Block
339
     */
340
    public function getRoot()
341
    {
342
        return $this->root;
343
    }
344
}
345