Issues (1236)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Psalm/Context.php (3 issues)

Upgrade to new PHP Analysis Engine

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
The doc-type array<int, could not be parsed: Expected ">" at position 5, but found "end of type". (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.

Loading history...
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.

Loading history...
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.

Loading history...
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