This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
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 | public function __construct($statement, Context $context) |
||
46 | { |
||
47 | $this->context = $context; |
||
48 | $this->root = new Block($this->lastBlockId++); |
||
49 | |||
50 | if ($statement instanceof Function_) { |
||
51 | if ($statement->stmts) { |
||
0 ignored issues
–
show
|
|||
52 | $this->passNodes($statement->stmts, $this->root); |
||
53 | } |
||
54 | } |
||
55 | |||
56 | if ($statement instanceof ClassMethod) { |
||
57 | if ($statement->stmts) { |
||
0 ignored issues
–
show
The expression
$statement->stmts of type PhpParser\Node\Stmt[] 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 ![]() |
|||
58 | $this->passNodes($statement->stmts, $this->root); |
||
59 | } |
||
60 | } |
||
61 | } |
||
62 | |||
63 | /** |
||
64 | * @param \PhpParser\Node[] $nodes |
||
65 | * @param Block $block |
||
66 | */ |
||
67 | protected function passNodes(array $nodes, Block $block) |
||
68 | { |
||
69 | foreach ($nodes as $stmt) { |
||
70 | switch (get_class($stmt)) { |
||
71 | case \PhpParser\Node\Stmt\Goto_::class: |
||
72 | if (isset($this->labels[$stmt->name])) { |
||
0 ignored issues
–
show
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
![]() |
|||
73 | $block->addChildren( |
||
74 | new Node\JumpNode($this->labels[$stmt->name]) |
||
0 ignored issues
–
show
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
![]() |
|||
75 | ); |
||
76 | } else { |
||
77 | $this->unresolvedGotos[] = $stmt; |
||
78 | } |
||
79 | break; |
||
80 | case \PhpParser\Node\Expr\Assign::class: |
||
81 | $this->passAssign($stmt, $block); |
||
0 ignored issues
–
show
$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. ![]() |
|||
82 | break; |
||
83 | case \PhpParser\Node\Stmt\Return_::class: |
||
84 | $block = $this->passReturn($stmt, $block); |
||
0 ignored issues
–
show
$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. ![]() |
|||
85 | break; |
||
86 | case \PhpParser\Node\Stmt\Foreach_::class: |
||
87 | $block = $this->passForeach($stmt, $block); |
||
0 ignored issues
–
show
$stmt of type object<PhpParser\Node> is not a sub-type of object<PhpParser\Node\Stmt\Foreach_> . 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. ![]() |
|||
88 | break; |
||
89 | case \PhpParser\Node\Stmt\For_::class: |
||
90 | $block = $this->passFor($stmt, $block); |
||
0 ignored issues
–
show
$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. ![]() |
|||
91 | break; |
||
92 | case \PhpParser\Node\Stmt\If_::class: |
||
93 | $block = $this->passIf($stmt, $block); |
||
0 ignored issues
–
show
$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. ![]() |
|||
94 | break; |
||
95 | case \PhpParser\Node\Stmt\While_::class: |
||
96 | $block = $this->passWhile($stmt, $this->createNewBlockIfNeeded($block)); |
||
0 ignored issues
–
show
$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. ![]() |
|||
97 | break; |
||
98 | case \PhpParser\Node\Stmt\Do_::class: |
||
99 | $block = $this->passDo($stmt, $block); |
||
0 ignored issues
–
show
$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. ![]() |
|||
100 | break; |
||
101 | case \PhpParser\Node\Stmt\Throw_::class: |
||
102 | $this->passThrow($stmt, $block); |
||
0 ignored issues
–
show
$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. ![]() |
|||
103 | break; |
||
104 | case \PhpParser\Node\Expr\Exit_::class: |
||
105 | $block->addChildren(new Node\ExitNode()); |
||
106 | break; |
||
107 | case \PhpParser\Node\Stmt\Label::class: |
||
108 | $block = $this->createNewBlockIfNeeded($block); |
||
109 | $block->label = $stmt->name; |
||
0 ignored issues
–
show
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
![]() |
|||
110 | $this->labels[$block->label] = $block; |
||
111 | break; |
||
112 | case \PhpParser\Node\Stmt\TryCatch::class: |
||
113 | $block = $this->passTryCatch($stmt, $block); |
||
0 ignored issues
–
show
$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. ![]() |
|||
114 | break; |
||
115 | case \PhpParser\Node\Stmt\Nop::class: |
||
116 | // ignore commented code |
||
117 | break; |
||
118 | default: |
||
119 | $this->context->debug('[CFG] Unimplemented ' . get_class($stmt), $stmt); |
||
120 | break; |
||
121 | } |
||
122 | } |
||
123 | } |
||
124 | |||
125 | /** |
||
126 | * If current block is not empty, lets create a new one |
||
127 | * |
||
128 | * @param Block $block |
||
129 | * @return Block |
||
130 | */ |
||
131 | protected function createNewBlockIfNeeded(Block $block) |
||
132 | { |
||
133 | if (!$block->getChildren()) { |
||
134 | $next = new Block($this->lastBlockId++); |
||
135 | |||
136 | $next->setExit( |
||
137 | $block |
||
138 | ); |
||
139 | |||
140 | return $next; |
||
141 | } |
||
142 | |||
143 | return $block; |
||
144 | } |
||
145 | |||
146 | /** |
||
147 | * @param \PhpParser\Node\Expr $expr |
||
148 | * @return Node\AbstractNode |
||
149 | */ |
||
150 | protected function passExpr(\PhpParser\Node\Expr $expr) |
||
151 | { |
||
152 | switch (get_class($expr)) { |
||
153 | case \PhpParser\Node\Expr\BinaryOp\NotIdentical::class: |
||
154 | return new Node\Expr\BinaryOp\NotIdentical(); |
||
155 | |||
156 | case \PhpParser\Node\Expr\BinaryOp\Identical::class: |
||
157 | return new Node\Expr\BinaryOp\Identical(); |
||
158 | |||
159 | case \PhpParser\Node\Expr\BinaryOp\NotEqual::class: |
||
160 | return new Node\Expr\BinaryOp\NotEqual(); |
||
161 | |||
162 | case \PhpParser\Node\Expr\BinaryOp\Equal::class: |
||
163 | return new Node\Expr\BinaryOp\Equal(); |
||
164 | |||
165 | case \PhpParser\Node\Expr\BinaryOp\Smaller::class: |
||
166 | return new Node\Expr\BinaryOp\Smaller(); |
||
167 | |||
168 | case \PhpParser\Node\Expr\BinaryOp\SmallerOrEqual::class: |
||
169 | return new Node\Expr\BinaryOp\SmallerOrEqual(); |
||
170 | |||
171 | case \PhpParser\Node\Expr\BinaryOp\Greater::class: |
||
172 | return new Node\Expr\BinaryOp\Greater(); |
||
173 | |||
174 | case \PhpParser\Node\Expr\BinaryOp\GreaterOrEqual::class: |
||
175 | return new Node\Expr\BinaryOp\GreaterOrEqual(); |
||
176 | |||
177 | case \PhpParser\Node\Expr\Instanceof_::class: |
||
178 | return new Node\Expr\InstanceOfExpr(); |
||
179 | |||
180 | default: |
||
181 | $this->context->debug('[CFG] Unimplemented ' . get_class($expr), $expr); |
||
182 | } |
||
183 | |||
184 | return new Node\UnknownNode(); |
||
185 | } |
||
186 | |||
187 | /** |
||
188 | * @param \PhpParser\Node\Stmt\If_ $if |
||
189 | * @param Block $block |
||
190 | * @return Block |
||
191 | */ |
||
192 | protected function passIf(\PhpParser\Node\Stmt\If_ $if, Block $block) |
||
0 ignored issues
–
show
|
|||
193 | { |
||
194 | $trueBlock = new Block($this->lastBlockId++); |
||
195 | $this->passNodes($if->stmts, $trueBlock); |
||
196 | |||
197 | $jumpIf = new Node\JumpIfNode($this->passExpr($if->cond), $trueBlock); |
||
198 | |||
199 | $elseBlock = null; |
||
200 | |||
201 | if ($if->else) { |
||
202 | if ($if->else->stmts) { |
||
0 ignored issues
–
show
The expression
$if->else->stmts of type PhpParser\Node\Stmt[] 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 ![]() |
|||
203 | $elseBlock = new Block($this->lastBlockId++); |
||
204 | $this->passNodes($if->else->stmts, $elseBlock); |
||
205 | |||
206 | $jumpIf->setElse($elseBlock); |
||
207 | } |
||
208 | } |
||
209 | |||
210 | $block->addChildren( |
||
211 | $jumpIf |
||
212 | ); |
||
213 | |||
214 | $exitBlock = new Block($this->lastBlockId++); |
||
215 | $trueBlock->setExit($exitBlock); |
||
216 | |||
217 | if ($elseBlock) { |
||
218 | $elseBlock->setExit($exitBlock); |
||
219 | } |
||
220 | |||
221 | return $exitBlock; |
||
222 | } |
||
223 | |||
224 | /** |
||
225 | * @param \PhpParser\Node\Stmt\Foreach_ $foreach |
||
226 | * @param Block $block |
||
227 | * @return Block |
||
228 | */ |
||
229 | protected function passForeach(\PhpParser\Node\Stmt\Foreach_ $foreach, Block $block) |
||
230 | { |
||
231 | $block->setExit( |
||
232 | $next = new Block($this->lastBlockId++) |
||
233 | ); |
||
234 | |||
235 | $next->setExit( |
||
236 | $loop = new Block($this->lastBlockId++) |
||
237 | ); |
||
238 | |||
239 | $this->passNodes($foreach->stmts, $loop); |
||
240 | |||
241 | $loop->addChildren(new Node\JumpNode($next)); |
||
242 | |||
243 | $loop->setExit( |
||
244 | $after = new Block($this->lastBlockId++) |
||
245 | ); |
||
246 | |||
247 | return $after; |
||
248 | } |
||
249 | |||
250 | /** |
||
251 | * @param \PhpParser\Node\Stmt\For_ $for |
||
252 | * @param Block $block |
||
253 | * @return Block |
||
254 | */ |
||
255 | protected function passFor(\PhpParser\Node\Stmt\For_ $for, Block $block) |
||
256 | { |
||
257 | $this->passNodes($for->init, $block); |
||
258 | |||
259 | $block->setExit( |
||
260 | $loop = new Block($this->lastBlockId++) |
||
261 | ); |
||
262 | $this->passNodes($for->stmts, $loop); |
||
263 | |||
264 | $loop->setExit( |
||
265 | $after = new Block($this->lastBlockId++) |
||
266 | ); |
||
267 | return $after; |
||
268 | } |
||
269 | |||
270 | /** |
||
271 | * @param \PhpParser\Node\Stmt\Do_ $do |
||
272 | * @param Block $block |
||
273 | * @return Block |
||
274 | */ |
||
275 | protected function passDo(\PhpParser\Node\Stmt\Do_ $do, Block $block) |
||
0 ignored issues
–
show
|
|||
276 | { |
||
277 | $loop = new Block($this->lastBlockId++); |
||
278 | $this->passNodes($do->stmts, $loop); |
||
279 | |||
280 | $block->setExit($loop); |
||
281 | |||
282 | $cond = new Block($this->lastBlockId++); |
||
283 | $loop->setExit($cond); |
||
284 | |||
285 | $jumpIf = new Node\JumpIfNode($this->passExpr($do->cond), $loop); |
||
286 | $cond->addChildren($jumpIf); |
||
287 | |||
288 | $exitBlock = new Block($this->lastBlockId++); |
||
289 | $jumpIf->setElse($exitBlock); |
||
290 | |||
291 | return $exitBlock; |
||
292 | } |
||
293 | |||
294 | /** |
||
295 | * @param \PhpParser\Node\Stmt\While_ $while |
||
296 | * @param Block $cond |
||
297 | * @return Block |
||
298 | */ |
||
299 | protected function passWhile(\PhpParser\Node\Stmt\While_ $while, Block $cond) |
||
300 | { |
||
301 | $loop = new Block($this->lastBlockId++); |
||
302 | |||
303 | $jumpIf = new Node\JumpIfNode($this->passExpr($while->cond), $loop); |
||
304 | $cond->addChildren($jumpIf); |
||
305 | |||
306 | $this->passNodes($while->stmts, $loop); |
||
307 | |||
308 | $loop->addChildren(new Node\JumpNode($cond)); |
||
309 | //$loop->setExit($cond); |
||
310 | |||
311 | $after = new Block($this->lastBlockId++); |
||
312 | $jumpIf->setElse($after); |
||
313 | |||
314 | return $after; |
||
315 | } |
||
316 | |||
317 | /** |
||
318 | * @param \PhpParser\Node\Stmt\Throw_ $throw_ |
||
319 | * @param Block $block |
||
320 | */ |
||
321 | protected function passThrow(\PhpParser\Node\Stmt\Throw_ $throw_, Block $block) |
||
0 ignored issues
–
show
|
|||
322 | { |
||
323 | $block->addChildren(new Node\ThrowNode()); |
||
324 | } |
||
325 | |||
326 | /** |
||
327 | * @param \PhpParser\Node\Expr\Assign $assign |
||
328 | * @param Block $block |
||
329 | */ |
||
330 | protected function passAssign(\PhpParser\Node\Expr\Assign $assign, Block $block) |
||
0 ignored issues
–
show
|
|||
331 | { |
||
332 | $block->addChildren(new Node\AssignNode()); |
||
333 | } |
||
334 | |||
335 | /** |
||
336 | * @param \PhpParser\Node\Stmt\Return_ $return |
||
337 | * @param Block $block |
||
338 | */ |
||
339 | protected function passReturn(\PhpParser\Node\Stmt\Return_ $return, Block $block) |
||
340 | { |
||
341 | if ($return->expr) { |
||
342 | $block->addChildren( |
||
343 | new Node\ReturnNode( |
||
344 | $this->passExpr( |
||
345 | $return->expr |
||
346 | ) |
||
347 | ) |
||
348 | ); |
||
349 | } else { |
||
350 | $block->addChildren( |
||
351 | new Node\ReturnNode() |
||
352 | ); |
||
353 | } |
||
354 | |||
355 | $unreachableBlock = new Block($this->lastBlockId++, true); |
||
356 | $block->setExit($unreachableBlock); |
||
357 | |||
358 | return $unreachableBlock; |
||
359 | } |
||
360 | |||
361 | /** |
||
362 | * @param \PhpParser\Node\Stmt\TryCatch $stmt |
||
363 | * @param Block $block |
||
364 | * @return Block |
||
365 | */ |
||
366 | protected function passTryCatch(\PhpParser\Node\Stmt\TryCatch $stmt, Block $block) |
||
367 | { |
||
368 | $try = new Block($this->lastBlockId++); |
||
369 | $this->passNodes($stmt->stmts, $try); |
||
370 | |||
371 | $block->setExit($try); |
||
372 | |||
373 | if ($stmt->finally) { |
||
374 | $finally = new Block($this->lastBlockId++); |
||
375 | $this->passNodes($stmt->finally->stmts, $finally); |
||
376 | |||
377 | $try->setExit($finally); |
||
378 | return $finally; |
||
379 | } |
||
380 | |||
381 | return $try; |
||
382 | } |
||
383 | |||
384 | /** |
||
385 | * @return Block |
||
386 | */ |
||
387 | public function getRoot() |
||
388 | { |
||
389 | return $this->root; |
||
390 | } |
||
391 | } |
||
392 |
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.