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 | namespace Psalm; |
||
3 | |||
4 | use function array_filter; |
||
5 | use function array_keys; |
||
6 | use function count; |
||
7 | use function in_array; |
||
8 | use function json_encode; |
||
9 | use function preg_match; |
||
10 | use function preg_quote; |
||
11 | use function preg_replace; |
||
12 | use Psalm\Internal\Analyzer\StatementsAnalyzer; |
||
13 | use Psalm\Internal\Clause; |
||
14 | use Psalm\Internal\MethodIdentifier; |
||
15 | use Psalm\Storage\FunctionLikeStorage; |
||
16 | use Psalm\Internal\Type\AssertionReconciler; |
||
17 | use Psalm\Type\Union; |
||
18 | use function strpos; |
||
19 | use function strtolower; |
||
20 | use function array_search; |
||
21 | use function is_int; |
||
22 | |||
23 | class Context |
||
24 | { |
||
25 | /** |
||
26 | * @var array<string, Type\Union> |
||
27 | */ |
||
28 | public $vars_in_scope = []; |
||
29 | |||
30 | /** |
||
31 | * @var array<string, bool> |
||
32 | */ |
||
33 | public $vars_possibly_in_scope = []; |
||
34 | |||
35 | /** |
||
36 | * Whether or not we're inside the conditional of an if/where etc. |
||
37 | * |
||
38 | * This changes whether or not the context is cloned |
||
39 | * |
||
40 | * @var bool |
||
41 | */ |
||
42 | public $inside_conditional = false; |
||
43 | |||
44 | /** |
||
45 | * Whether or not we're inside a __construct function |
||
46 | * |
||
47 | * @var bool |
||
48 | */ |
||
49 | public $inside_constructor = false; |
||
50 | |||
51 | /** |
||
52 | * Whether or not we're inside an isset call |
||
53 | * |
||
54 | * Inside isssets Psalm is more lenient about certain things |
||
55 | * |
||
56 | * @var bool |
||
57 | */ |
||
58 | public $inside_isset = false; |
||
59 | |||
60 | /** |
||
61 | * Whether or not we're inside an unset call, where |
||
62 | * we don't care about possibly undefined variables |
||
63 | * |
||
64 | * @var bool |
||
65 | */ |
||
66 | public $inside_unset = false; |
||
67 | |||
68 | /** |
||
69 | * Whether or not we're inside an class_exists call, where |
||
70 | * we don't care about possibly undefined classes |
||
71 | * |
||
72 | * @var bool |
||
73 | */ |
||
74 | public $inside_class_exists = false; |
||
75 | |||
76 | /** |
||
77 | * Whether or not we're inside a function/method call |
||
78 | * |
||
79 | * @var bool |
||
80 | */ |
||
81 | public $inside_call = false; |
||
82 | |||
83 | /** |
||
84 | * Whether or not we're inside a throw |
||
85 | * |
||
86 | * @var bool |
||
87 | */ |
||
88 | public $inside_throw = false; |
||
89 | |||
90 | /** |
||
91 | * Whether or not we're inside an assignment |
||
92 | * |
||
93 | * @var bool |
||
94 | */ |
||
95 | public $inside_assignment = false; |
||
96 | |||
97 | /** |
||
98 | * @var null|CodeLocation |
||
99 | */ |
||
100 | public $include_location = null; |
||
101 | |||
102 | /** |
||
103 | * @var string|null |
||
104 | */ |
||
105 | public $self; |
||
106 | |||
107 | /** |
||
108 | * @var string|null |
||
109 | */ |
||
110 | public $parent; |
||
111 | |||
112 | /** |
||
113 | * @var bool |
||
114 | */ |
||
115 | public $check_classes = true; |
||
116 | |||
117 | /** |
||
118 | * @var bool |
||
119 | */ |
||
120 | public $check_variables = true; |
||
121 | |||
122 | /** |
||
123 | * @var bool |
||
124 | */ |
||
125 | public $check_methods = true; |
||
126 | |||
127 | /** |
||
128 | * @var bool |
||
129 | */ |
||
130 | public $check_consts = true; |
||
131 | |||
132 | /** |
||
133 | * @var bool |
||
134 | */ |
||
135 | public $check_functions = true; |
||
136 | |||
137 | /** |
||
138 | * A list of classes checked with class_exists |
||
139 | * |
||
140 | * @var array<string,bool> |
||
141 | */ |
||
142 | public $phantom_classes = []; |
||
143 | |||
144 | /** |
||
145 | * A list of files checked with file_exists |
||
146 | * |
||
147 | * @var array<string,bool> |
||
148 | */ |
||
149 | public $phantom_files = []; |
||
150 | |||
151 | /** |
||
152 | * A list of clauses in Conjunctive Normal Form |
||
153 | * |
||
154 | * @var list<Clause> |
||
155 | */ |
||
156 | public $clauses = []; |
||
157 | |||
158 | /** |
||
159 | * A list of hashed clauses that have already been factored in |
||
160 | * |
||
161 | * @var list<string> |
||
162 | */ |
||
163 | public $reconciled_expression_clauses = []; |
||
164 | |||
165 | /** |
||
166 | * Whether or not to do a deep analysis and collect mutations to this context |
||
167 | * |
||
168 | * @var bool |
||
169 | */ |
||
170 | public $collect_mutations = false; |
||
171 | |||
172 | /** |
||
173 | * Whether or not to do a deep analysis and collect initializations from private or final methods |
||
174 | * |
||
175 | * @var bool |
||
176 | */ |
||
177 | public $collect_initializations = false; |
||
178 | |||
179 | /** |
||
180 | * Whether or not to do a deep analysis and collect initializations from public non-final methods |
||
181 | * |
||
182 | * @var bool |
||
183 | */ |
||
184 | public $collect_nonprivate_initializations = false; |
||
185 | |||
186 | /** |
||
187 | * Stored to prevent re-analysing methods when checking for initialised properties |
||
188 | * |
||
189 | * @var array<string, bool>|null |
||
190 | */ |
||
191 | public $initialized_methods = null; |
||
192 | |||
193 | /** |
||
194 | * @var array<string, Type\Union> |
||
195 | */ |
||
196 | public $constants = []; |
||
197 | |||
198 | /** |
||
199 | * Whether or not to track exceptions |
||
200 | * |
||
201 | * @var bool |
||
202 | */ |
||
203 | public $collect_exceptions = false; |
||
204 | |||
205 | /** |
||
206 | * A list of variables that have been referenced |
||
207 | * |
||
208 | * @var array<string, bool> |
||
209 | */ |
||
210 | public $referenced_var_ids = []; |
||
211 | |||
212 | /** |
||
213 | * A list of variables that have never been referenced |
||
214 | * |
||
215 | * @var array<string, array<string, CodeLocation>> |
||
216 | */ |
||
217 | public $unreferenced_vars = []; |
||
218 | |||
219 | /** |
||
220 | * A list of variables that have been passed by reference (where we know their type) |
||
221 | * |
||
222 | * @var array<string, \Psalm\Internal\ReferenceConstraint>|null |
||
223 | */ |
||
224 | public $byref_constraints; |
||
225 | |||
226 | /** |
||
227 | * If this context inherits from a context, it is here |
||
228 | * |
||
229 | * @var Context|null |
||
230 | */ |
||
231 | public $parent_context; |
||
232 | |||
233 | /** |
||
234 | * @var array<string, Type\Union> |
||
235 | */ |
||
236 | public $possible_param_types = []; |
||
237 | |||
238 | /** |
||
239 | * A list of vars that have been assigned to |
||
240 | * |
||
241 | * @var array<string, bool> |
||
242 | */ |
||
243 | public $assigned_var_ids = []; |
||
244 | |||
245 | /** |
||
246 | * A list of vars that have been may have been assigned to |
||
247 | * |
||
248 | * @var array<string, bool> |
||
249 | */ |
||
250 | public $possibly_assigned_var_ids = []; |
||
251 | |||
252 | /** |
||
253 | * A list of classes or interfaces that may have been thrown |
||
254 | * |
||
255 | * @var array<string, array<array-key, CodeLocation>> |
||
256 | */ |
||
257 | public $possibly_thrown_exceptions = []; |
||
258 | |||
259 | /** |
||
260 | * @var bool |
||
261 | */ |
||
262 | public $is_global = false; |
||
263 | |||
264 | /** |
||
265 | * @var array<string, bool> |
||
266 | */ |
||
267 | public $protected_var_ids = []; |
||
268 | |||
269 | /** |
||
270 | * If we've branched from the main scope, a byte offset for where that branch happened |
||
271 | * |
||
272 | * @var int|null |
||
273 | */ |
||
274 | public $branch_point; |
||
275 | |||
276 | /** |
||
277 | * What does break mean in this context? |
||
278 | * |
||
279 | * 'loop' means we're breaking out of a loop, |
||
280 | * 'switch' means we're breaking out of a switch |
||
281 | * |
||
282 | * @var list<'loop'|'switch'> |
||
283 | */ |
||
284 | public $break_types = []; |
||
285 | |||
286 | /** |
||
287 | * @var bool |
||
288 | */ |
||
289 | public $inside_loop = false; |
||
290 | |||
291 | /** |
||
292 | * @var Internal\Scope\LoopScope|null |
||
293 | */ |
||
294 | public $loop_scope = null; |
||
295 | |||
296 | /** |
||
297 | * @var Internal\Scope\CaseScope|null |
||
298 | */ |
||
299 | public $case_scope = null; |
||
300 | |||
301 | /** |
||
302 | * @var Context|null |
||
303 | */ |
||
304 | public $if_context = null; |
||
305 | |||
306 | /** |
||
307 | * @var \Psalm\Internal\Scope\IfScope|null |
||
308 | */ |
||
309 | public $if_scope = null; |
||
310 | |||
311 | /** |
||
312 | * @var bool |
||
313 | */ |
||
314 | public $strict_types = false; |
||
315 | |||
316 | /** |
||
317 | * @var string|null |
||
318 | */ |
||
319 | public $calling_function_id; |
||
320 | |||
321 | /** |
||
322 | * @var lowercase-string|null |
||
323 | */ |
||
324 | public $calling_method_id; |
||
325 | |||
326 | /** |
||
327 | * @var bool |
||
328 | */ |
||
329 | public $inside_negation = false; |
||
330 | |||
331 | /** |
||
332 | * @var bool |
||
333 | */ |
||
334 | public $ignore_variable_property = false; |
||
335 | |||
336 | /** |
||
337 | * @var bool |
||
338 | */ |
||
339 | public $ignore_variable_method = false; |
||
340 | |||
341 | /** |
||
342 | * @var bool |
||
343 | */ |
||
344 | public $pure = false; |
||
345 | |||
346 | /** |
||
347 | * @var bool |
||
348 | */ |
||
349 | public $mutation_free = false; |
||
350 | |||
351 | /** |
||
352 | * @var bool |
||
353 | */ |
||
354 | public $external_mutation_free = false; |
||
355 | |||
356 | /** |
||
357 | * @var bool |
||
358 | */ |
||
359 | public $error_suppressing = false; |
||
360 | |||
361 | /** |
||
362 | * @var bool |
||
363 | */ |
||
364 | public $has_returned = false; |
||
365 | |||
366 | /** |
||
367 | * @param string|null $self |
||
368 | */ |
||
369 | public function __construct($self = null) |
||
370 | { |
||
371 | $this->self = $self; |
||
372 | } |
||
373 | |||
374 | public function __destruct() |
||
375 | { |
||
376 | $this->case_scope = null; |
||
377 | $this->parent_context = null; |
||
378 | } |
||
379 | |||
380 | /** |
||
381 | * @return void |
||
382 | */ |
||
383 | public function __clone() |
||
384 | { |
||
385 | foreach ($this->clauses as &$clause) { |
||
386 | $clause = clone $clause; |
||
387 | } |
||
388 | |||
389 | foreach ($this->constants as &$constant) { |
||
390 | $constant = clone $constant; |
||
391 | } |
||
392 | } |
||
393 | |||
394 | /** |
||
395 | * Updates the parent context, looking at the changes within a block and then applying those changes, where |
||
396 | * necessary, to the parent context |
||
397 | * |
||
398 | * @param Context $start_context |
||
399 | * @param Context $end_context |
||
400 | * @param bool $has_leaving_statements whether or not the parent scope is abandoned between |
||
401 | * $start_context and $end_context |
||
402 | * @param array $vars_to_update |
||
403 | * @param array<string, bool> $updated_vars |
||
404 | * |
||
405 | * @return void |
||
406 | */ |
||
407 | public function update( |
||
408 | Context $start_context, |
||
409 | Context $end_context, |
||
410 | $has_leaving_statements, |
||
411 | array $vars_to_update, |
||
412 | array &$updated_vars |
||
413 | ) { |
||
414 | foreach ($start_context->vars_in_scope as $var_id => $old_type) { |
||
415 | // this is only true if there was some sort of type negation |
||
416 | if (in_array($var_id, $vars_to_update, true)) { |
||
417 | // if we're leaving, we're effectively deleting the possibility of the if types |
||
418 | $new_type = !$has_leaving_statements && $end_context->hasVariable($var_id) |
||
419 | ? $end_context->vars_in_scope[$var_id] |
||
420 | : null; |
||
421 | |||
422 | $existing_type = isset($this->vars_in_scope[$var_id]) ? $this->vars_in_scope[$var_id] : null; |
||
423 | |||
424 | if (!$existing_type) { |
||
425 | if ($new_type) { |
||
426 | $this->vars_in_scope[$var_id] = clone $new_type; |
||
427 | $updated_vars[$var_id] = true; |
||
428 | } |
||
429 | |||
430 | continue; |
||
431 | } |
||
432 | |||
433 | $existing_type = clone $existing_type; |
||
434 | |||
435 | // if the type changed within the block of statements, process the replacement |
||
436 | // also never allow ourselves to remove all types from a union |
||
437 | if ((!$new_type || !$old_type->equals($new_type)) |
||
438 | && ($new_type || count($existing_type->getAtomicTypes()) > 1) |
||
439 | ) { |
||
440 | $existing_type->substitute($old_type, $new_type); |
||
441 | |||
442 | if ($new_type && $new_type->from_docblock) { |
||
443 | $existing_type->setFromDocblock(); |
||
444 | } |
||
445 | |||
446 | $updated_vars[$var_id] = true; |
||
447 | } |
||
448 | |||
449 | $this->vars_in_scope[$var_id] = $existing_type; |
||
450 | } |
||
451 | } |
||
452 | } |
||
453 | |||
454 | /** |
||
455 | * @param array<string, Type\Union> $new_vars_in_scope |
||
456 | * @param bool $include_new_vars |
||
457 | * |
||
458 | * @return array<string,Type\Union> |
||
459 | */ |
||
460 | public function getRedefinedVars(array $new_vars_in_scope, $include_new_vars = false) |
||
461 | { |
||
462 | $redefined_vars = []; |
||
463 | |||
464 | foreach ($this->vars_in_scope as $var_id => $this_type) { |
||
465 | if (!isset($new_vars_in_scope[$var_id])) { |
||
466 | if ($include_new_vars) { |
||
467 | $redefined_vars[$var_id] = $this_type; |
||
468 | } |
||
469 | continue; |
||
470 | } |
||
471 | |||
472 | $new_type = $new_vars_in_scope[$var_id]; |
||
473 | |||
474 | if (!$this_type->failed_reconciliation |
||
475 | && !$this_type->isEmpty() |
||
476 | && !$new_type->isEmpty() |
||
477 | && !$this_type->equals($new_type) |
||
478 | ) { |
||
479 | $redefined_vars[$var_id] = $this_type; |
||
480 | } |
||
481 | } |
||
482 | |||
483 | return $redefined_vars; |
||
484 | } |
||
485 | |||
486 | /** |
||
487 | * @param Context $original_context |
||
488 | * @param Context $new_context |
||
489 | * |
||
490 | * @return array<int, string> |
||
0 ignored issues
–
show
|
|||
491 | */ |
||
492 | public static function getNewOrUpdatedVarIds(Context $original_context, Context $new_context) |
||
493 | { |
||
494 | $redefined_var_ids = []; |
||
495 | |||
496 | foreach ($new_context->vars_in_scope as $var_id => $context_type) { |
||
497 | if (!isset($original_context->vars_in_scope[$var_id]) |
||
498 | || !$original_context->vars_in_scope[$var_id]->equals($context_type) |
||
499 | ) { |
||
500 | $redefined_var_ids[] = $var_id; |
||
501 | } |
||
502 | } |
||
503 | |||
504 | return $redefined_var_ids; |
||
505 | } |
||
506 | |||
507 | /** |
||
508 | * @param string $remove_var_id |
||
509 | * |
||
510 | * @return void |
||
511 | */ |
||
512 | public function remove($remove_var_id) |
||
513 | { |
||
514 | unset( |
||
515 | $this->referenced_var_ids[$remove_var_id], |
||
516 | $this->vars_possibly_in_scope[$remove_var_id] |
||
517 | ); |
||
518 | |||
519 | if (isset($this->vars_in_scope[$remove_var_id])) { |
||
520 | $existing_type = $this->vars_in_scope[$remove_var_id]; |
||
521 | unset($this->vars_in_scope[$remove_var_id]); |
||
522 | |||
523 | $this->removeDescendents($remove_var_id, $existing_type); |
||
524 | } |
||
525 | } |
||
526 | |||
527 | /** |
||
528 | * @param Clause[] $clauses |
||
529 | * @param array<string, bool> $changed_var_ids |
||
530 | * |
||
531 | * @return array{0: list<Clause>, list<Clause>} |
||
0 ignored issues
–
show
The doc-type
array{0: could not be parsed: Unknown type name "array{0:" at position 0. (view supported doc-types)
This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types. ![]() |
|||
532 | */ |
||
533 | public static function removeReconciledClauses(array $clauses, array $changed_var_ids) |
||
534 | { |
||
535 | $included_clauses = []; |
||
536 | $rejected_clauses = []; |
||
537 | |||
538 | foreach ($clauses as $c) { |
||
539 | if ($c->wedge) { |
||
540 | $included_clauses[] = $c; |
||
541 | continue; |
||
542 | } |
||
543 | |||
544 | foreach ($c->possibilities as $key => $_) { |
||
545 | if (isset($changed_var_ids[$key])) { |
||
546 | $rejected_clauses[] = $c; |
||
547 | continue 2; |
||
548 | } |
||
549 | } |
||
550 | |||
551 | $included_clauses[] = $c; |
||
552 | } |
||
553 | |||
554 | return [$included_clauses, $rejected_clauses]; |
||
555 | } |
||
556 | |||
557 | /** |
||
558 | * @param string $remove_var_id |
||
559 | * @param Clause[] $clauses |
||
560 | * @param Union|null $new_type |
||
561 | * @param StatementsAnalyzer|null $statements_analyzer |
||
562 | * |
||
563 | * @return list<Clause> |
||
0 ignored issues
–
show
The doc-type
list<Clause> could not be parsed: Expected "|" or "end of type", but got "<" at position 4. (view supported doc-types)
This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types. ![]() |
|||
564 | */ |
||
565 | public static function filterClauses( |
||
566 | $remove_var_id, |
||
567 | array $clauses, |
||
568 | Union $new_type = null, |
||
569 | StatementsAnalyzer $statements_analyzer = null |
||
570 | ) { |
||
571 | $new_type_string = $new_type ? $new_type->getId() : ''; |
||
572 | |||
573 | $clauses_to_keep = []; |
||
574 | |||
575 | foreach ($clauses as $clause) { |
||
576 | \Psalm\Type\Algebra::calculateNegation($clause); |
||
577 | |||
578 | $quoted_remove_var_id = preg_quote($remove_var_id, '/'); |
||
579 | |||
580 | foreach ($clause->possibilities as $var_id => $_) { |
||
581 | if (preg_match('/' . $quoted_remove_var_id . '[\]\[\-]/', $var_id)) { |
||
582 | break 2; |
||
583 | } |
||
584 | } |
||
585 | |||
586 | if (!isset($clause->possibilities[$remove_var_id]) || |
||
587 | $clause->possibilities[$remove_var_id] === [$new_type_string] |
||
588 | ) { |
||
589 | $clauses_to_keep[] = $clause; |
||
590 | } elseif ($statements_analyzer && |
||
591 | $new_type && |
||
592 | !$new_type->hasMixed() |
||
593 | ) { |
||
594 | $type_changed = false; |
||
595 | |||
596 | // if the clause contains any possibilities that would be altered |
||
597 | // by the new type |
||
598 | foreach ($clause->possibilities[$remove_var_id] as $type) { |
||
599 | // if we're negating a type, we generally don't need the clause anymore |
||
600 | if ($type[0] === '!' && $type !== '!falsy' && $type !== '!empty') { |
||
601 | $type_changed = true; |
||
602 | break; |
||
603 | } |
||
604 | |||
605 | // empty and !empty are not definitive for arrays and scalar types |
||
606 | if (($type === '!falsy' || $type === 'falsy') && |
||
607 | ($new_type->hasArray() || $new_type->hasPossiblyNumericType()) |
||
608 | ) { |
||
609 | $type_changed = true; |
||
610 | break; |
||
611 | } |
||
612 | |||
613 | $result_type = AssertionReconciler::reconcile( |
||
614 | $type, |
||
615 | clone $new_type, |
||
616 | null, |
||
617 | $statements_analyzer, |
||
618 | false, |
||
619 | [], |
||
620 | null, |
||
621 | [], |
||
622 | $failed_reconciliation |
||
623 | ); |
||
624 | |||
625 | if ($result_type->getId() !== $new_type_string) { |
||
626 | $type_changed = true; |
||
627 | break; |
||
628 | } |
||
629 | } |
||
630 | |||
631 | if (!$type_changed) { |
||
632 | $clauses_to_keep[] = $clause; |
||
633 | } |
||
634 | } |
||
635 | } |
||
636 | |||
637 | return $clauses_to_keep; |
||
638 | } |
||
639 | |||
640 | /** |
||
641 | * @param string $remove_var_id |
||
642 | * @param Union|null $new_type |
||
643 | * @param null|StatementsAnalyzer $statements_analyzer |
||
644 | * |
||
645 | * @return void |
||
646 | */ |
||
647 | public function removeVarFromConflictingClauses( |
||
648 | $remove_var_id, |
||
649 | Union $new_type = null, |
||
650 | StatementsAnalyzer $statements_analyzer = null |
||
651 | ) { |
||
652 | $this->clauses = self::filterClauses($remove_var_id, $this->clauses, $new_type, $statements_analyzer); |
||
653 | |||
654 | if ($this->parent_context) { |
||
655 | $this->parent_context->removeVarFromConflictingClauses($remove_var_id); |
||
656 | } |
||
657 | } |
||
658 | |||
659 | /** |
||
660 | * @param string $remove_var_id |
||
661 | * @param \Psalm\Type\Union|null $existing_type |
||
662 | * @param \Psalm\Type\Union|null $new_type |
||
663 | * @param null|StatementsAnalyzer $statements_analyzer |
||
664 | * |
||
665 | * @return void |
||
666 | */ |
||
667 | public function removeDescendents( |
||
668 | $remove_var_id, |
||
669 | Union $existing_type = null, |
||
670 | Union $new_type = null, |
||
671 | StatementsAnalyzer $statements_analyzer = null |
||
672 | ) { |
||
673 | if (!$existing_type && isset($this->vars_in_scope[$remove_var_id])) { |
||
674 | $existing_type = $this->vars_in_scope[$remove_var_id]; |
||
675 | } |
||
676 | |||
677 | if (!$existing_type) { |
||
678 | return; |
||
679 | } |
||
680 | |||
681 | $this->removeVarFromConflictingClauses( |
||
682 | $remove_var_id, |
||
683 | $existing_type->hasMixed() |
||
684 | || ($new_type && $existing_type->from_docblock !== $new_type->from_docblock) |
||
685 | ? null |
||
686 | : $new_type, |
||
687 | $statements_analyzer |
||
688 | ); |
||
689 | |||
690 | $vars_to_remove = []; |
||
691 | |||
692 | foreach ($this->vars_in_scope as $var_id => $_) { |
||
693 | if (preg_match('/' . preg_quote($remove_var_id, '/') . '[\]\[\-]/', $var_id)) { |
||
694 | $vars_to_remove[] = $var_id; |
||
695 | } |
||
696 | } |
||
697 | |||
698 | foreach ($vars_to_remove as $var_id) { |
||
699 | unset($this->vars_in_scope[$var_id]); |
||
700 | } |
||
701 | } |
||
702 | |||
703 | /** |
||
704 | * @return void |
||
705 | */ |
||
706 | public function removeAllObjectVars() |
||
707 | { |
||
708 | $vars_to_remove = []; |
||
709 | |||
710 | foreach ($this->vars_in_scope as $var_id => $_) { |
||
711 | if (strpos($var_id, '->') !== false || strpos($var_id, '::') !== false) { |
||
712 | $vars_to_remove[] = $var_id; |
||
713 | } |
||
714 | } |
||
715 | |||
716 | if (!$vars_to_remove) { |
||
717 | return; |
||
718 | } |
||
719 | |||
720 | foreach ($vars_to_remove as $var_id) { |
||
721 | unset($this->vars_in_scope[$var_id], $this->vars_possibly_in_scope[$var_id]); |
||
722 | } |
||
723 | |||
724 | $clauses_to_keep = []; |
||
725 | |||
726 | foreach ($this->clauses as $clause) { |
||
727 | $abandon_clause = false; |
||
728 | |||
729 | foreach (array_keys($clause->possibilities) as $key) { |
||
730 | if (strpos($key, '->') !== false || strpos($key, '::') !== false) { |
||
731 | $abandon_clause = true; |
||
732 | break; |
||
733 | } |
||
734 | } |
||
735 | |||
736 | if (!$abandon_clause) { |
||
737 | $clauses_to_keep[] = $clause; |
||
738 | } |
||
739 | } |
||
740 | |||
741 | $this->clauses = $clauses_to_keep; |
||
742 | } |
||
743 | |||
744 | /** |
||
745 | * @param Context $op_context |
||
746 | * |
||
747 | * @return void |
||
748 | */ |
||
749 | public function updateChecks(Context $op_context) |
||
750 | { |
||
751 | $this->check_classes = $this->check_classes && $op_context->check_classes; |
||
752 | $this->check_variables = $this->check_variables && $op_context->check_variables; |
||
753 | $this->check_methods = $this->check_methods && $op_context->check_methods; |
||
754 | $this->check_functions = $this->check_functions && $op_context->check_functions; |
||
755 | $this->check_consts = $this->check_consts && $op_context->check_consts; |
||
756 | } |
||
757 | |||
758 | /** |
||
759 | * @param string $class_name |
||
760 | * |
||
761 | * @return bool |
||
762 | */ |
||
763 | public function isPhantomClass($class_name) |
||
764 | { |
||
765 | return isset($this->phantom_classes[strtolower($class_name)]); |
||
766 | } |
||
767 | |||
768 | /** |
||
769 | * @param string|null $var_name |
||
770 | * |
||
771 | * @return bool |
||
772 | */ |
||
773 | public function hasVariable($var_name, StatementsAnalyzer $statements_analyzer = null) |
||
774 | { |
||
775 | if (!$var_name) { |
||
776 | return false; |
||
777 | } |
||
778 | |||
779 | $stripped_var = preg_replace('/(->|\[).*$/', '', $var_name); |
||
780 | |||
781 | if ($stripped_var !== '$this' || $var_name !== $stripped_var) { |
||
782 | $this->referenced_var_ids[$var_name] = true; |
||
783 | |||
784 | if (!isset($this->vars_possibly_in_scope[$var_name]) |
||
785 | && !isset($this->vars_in_scope[$var_name]) |
||
786 | ) { |
||
787 | return false; |
||
788 | } |
||
789 | |||
790 | if ($statements_analyzer && $statements_analyzer->getCodebase()->find_unused_variables) { |
||
791 | if (isset($this->unreferenced_vars[$var_name])) { |
||
792 | $statements_analyzer->registerVariableUses($this->unreferenced_vars[$var_name]); |
||
793 | } |
||
794 | |||
795 | unset($this->unreferenced_vars[$var_name]); |
||
796 | } |
||
797 | } |
||
798 | |||
799 | return isset($this->vars_in_scope[$var_name]); |
||
800 | } |
||
801 | |||
802 | public function getScopeSummary() : string |
||
803 | { |
||
804 | $summary = []; |
||
805 | foreach ($this->vars_possibly_in_scope as $k => $_) { |
||
806 | $summary[$k] = true; |
||
807 | } |
||
808 | foreach ($this->vars_in_scope as $k => $v) { |
||
809 | $summary[$k] = $v->getId(); |
||
810 | } |
||
811 | |||
812 | return json_encode($summary); |
||
813 | } |
||
814 | |||
815 | /** |
||
816 | * @return void |
||
817 | */ |
||
818 | public function defineGlobals() |
||
819 | { |
||
820 | $globals = [ |
||
821 | '$argv' => new Type\Union([ |
||
822 | new Type\Atomic\TArray([Type::getInt(), Type::getString()]), |
||
823 | ]), |
||
824 | '$argc' => Type::getInt(), |
||
825 | ]; |
||
826 | |||
827 | $config = Config::getInstance(); |
||
828 | |||
829 | foreach ($config->globals as $global_id => $type_string) { |
||
830 | $globals[$global_id] = Type::parseString($type_string); |
||
831 | } |
||
832 | |||
833 | foreach ($globals as $global_id => $type) { |
||
834 | $this->vars_in_scope[$global_id] = $type; |
||
835 | $this->vars_possibly_in_scope[$global_id] = true; |
||
836 | } |
||
837 | } |
||
838 | |||
839 | /** |
||
840 | * @return void |
||
841 | */ |
||
842 | public function mergeExceptions(Context $other_context) |
||
843 | { |
||
844 | foreach ($other_context->possibly_thrown_exceptions as $possibly_thrown_exception => $codelocations) { |
||
845 | foreach ($codelocations as $hash => $codelocation) { |
||
846 | $this->possibly_thrown_exceptions[$possibly_thrown_exception][$hash] = $codelocation; |
||
847 | } |
||
848 | } |
||
849 | } |
||
850 | |||
851 | /** |
||
852 | * @return bool |
||
853 | */ |
||
854 | public function isSuppressingExceptions(StatementsAnalyzer $statements_analyzer) |
||
855 | { |
||
856 | if (!$this->collect_exceptions) { |
||
857 | return true; |
||
858 | } |
||
859 | |||
860 | $issue_type = $this->is_global ? 'UncaughtThrowInGlobalScope' : 'MissingThrowsDocblock'; |
||
861 | $suppressed_issues = $statements_analyzer->getSuppressedIssues(); |
||
862 | $suppressed_issue_position = array_search($issue_type, $suppressed_issues, true); |
||
863 | if ($suppressed_issue_position !== false) { |
||
864 | if (is_int($suppressed_issue_position)) { |
||
865 | $file = $statements_analyzer->getFileAnalyzer()->getFilePath(); |
||
866 | IssueBuffer::addUsedSuppressions([ |
||
867 | $file => [$suppressed_issue_position => true], |
||
868 | ]); |
||
869 | } |
||
870 | return true; |
||
871 | } |
||
872 | |||
873 | return false; |
||
874 | } |
||
875 | |||
876 | /** |
||
877 | * @return void |
||
878 | */ |
||
879 | public function mergeFunctionExceptions( |
||
880 | FunctionLikeStorage $function_storage, |
||
881 | CodeLocation $codelocation |
||
882 | ) { |
||
883 | $hash = $codelocation->getHash(); |
||
884 | foreach ($function_storage->throws as $possibly_thrown_exception => $_) { |
||
885 | $this->possibly_thrown_exceptions[$possibly_thrown_exception][$hash] = $codelocation; |
||
886 | } |
||
887 | } |
||
888 | } |
||
889 |
This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.