2016-10-22 19:23:18 +02:00
|
|
|
<?php
|
2018-11-06 03:57:36 +01:00
|
|
|
namespace Psalm\Internal\Analyzer\Statements\Block;
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
use PhpParser;
|
2018-11-06 03:57:36 +01:00
|
|
|
use Psalm\Codebase;
|
|
|
|
use Psalm\Internal\Analyzer\AlgebraAnalyzer;
|
|
|
|
use Psalm\Internal\Analyzer\ScopeAnalyzer;
|
|
|
|
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
|
|
|
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
|
|
|
use Psalm\Internal\Analyzer\TypeAnalyzer;
|
|
|
|
use Psalm\Internal\Clause;
|
2016-12-04 01:11:30 +01:00
|
|
|
use Psalm\CodeLocation;
|
2016-10-22 19:23:18 +02:00
|
|
|
use Psalm\Context;
|
2017-02-23 06:25:28 +01:00
|
|
|
use Psalm\Issue\ConflictingReferenceConstraint;
|
2017-05-19 06:48:26 +02:00
|
|
|
use Psalm\IssueBuffer;
|
2018-11-06 03:57:36 +01:00
|
|
|
use Psalm\Internal\Scope\IfScope;
|
2016-10-22 19:23:18 +02:00
|
|
|
use Psalm\Type;
|
2018-05-07 07:26:06 +02:00
|
|
|
use Psalm\Type\Algebra;
|
2017-12-29 16:55:41 +01:00
|
|
|
use Psalm\Type\Reconciler;
|
2019-06-26 22:52:29 +02:00
|
|
|
use function array_merge;
|
|
|
|
use function array_map;
|
|
|
|
use function array_diff_key;
|
|
|
|
use function array_filter;
|
|
|
|
use const ARRAY_FILTER_USE_KEY;
|
|
|
|
use function array_values;
|
|
|
|
use function array_keys;
|
2019-10-18 21:06:37 +02:00
|
|
|
use function array_reduce;
|
|
|
|
use function array_combine;
|
2019-06-26 22:52:29 +02:00
|
|
|
use function preg_match;
|
|
|
|
use function preg_quote;
|
|
|
|
use function array_unique;
|
|
|
|
use function count;
|
|
|
|
use function in_array;
|
|
|
|
use function array_intersect;
|
|
|
|
use function strpos;
|
|
|
|
use function substr;
|
|
|
|
use function array_intersect_key;
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2018-12-02 00:37:49 +01:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2018-11-06 03:57:36 +01:00
|
|
|
class IfAnalyzer
|
2016-10-22 19:23:18 +02:00
|
|
|
{
|
|
|
|
/**
|
|
|
|
* System of type substitution and deletion
|
|
|
|
*
|
|
|
|
* for example
|
|
|
|
*
|
|
|
|
* x: A|null
|
|
|
|
*
|
|
|
|
* if (x)
|
|
|
|
* (x: A)
|
|
|
|
* x = B -- effects: remove A from the type of x, add B
|
|
|
|
* else
|
|
|
|
* (x: null)
|
|
|
|
* x = C -- effects: remove null from the type of x, add C
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* x: A|null
|
|
|
|
*
|
|
|
|
* if (!x)
|
|
|
|
* (x: null)
|
|
|
|
* throw new Exception -- effects: remove null from the type of x
|
|
|
|
*
|
2018-11-11 18:01:14 +01:00
|
|
|
* @param StatementsAnalyzer $statements_analyzer
|
2016-10-22 19:23:18 +02:00
|
|
|
* @param PhpParser\Node\Stmt\If_ $stmt
|
|
|
|
* @param Context $context
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-10-22 19:23:18 +02:00
|
|
|
* @return null|false
|
|
|
|
*/
|
2017-01-07 21:09:47 +01:00
|
|
|
public static function analyze(
|
2018-11-11 18:01:14 +01:00
|
|
|
StatementsAnalyzer $statements_analyzer,
|
2016-11-02 07:29:00 +01:00
|
|
|
PhpParser\Node\Stmt\If_ $stmt,
|
2018-06-17 03:54:44 +02:00
|
|
|
Context $context
|
2016-11-02 07:29:00 +01:00
|
|
|
) {
|
2018-11-11 18:01:14 +01:00
|
|
|
$codebase = $statements_analyzer->getCodebase();
|
2018-11-06 03:57:36 +01:00
|
|
|
|
2016-11-11 23:13:13 +01:00
|
|
|
$if_scope = new IfScope();
|
|
|
|
|
2019-08-27 04:16:06 +02:00
|
|
|
try {
|
|
|
|
$if_conditional_scope = self::analyzeIfConditional(
|
|
|
|
$statements_analyzer,
|
2019-08-27 04:54:54 +02:00
|
|
|
$stmt->cond,
|
2019-08-27 04:16:06 +02:00
|
|
|
$context,
|
2019-08-27 04:54:54 +02:00
|
|
|
$codebase,
|
|
|
|
$if_scope,
|
|
|
|
$context->branch_point ?: (int) $stmt->getAttribute('startFilePos')
|
2018-06-07 21:04:16 +02:00
|
|
|
);
|
|
|
|
|
2019-08-27 04:16:06 +02:00
|
|
|
$if_context = $if_conditional_scope->if_context;
|
|
|
|
$original_context = $if_conditional_scope->original_context;
|
|
|
|
$cond_referenced_var_ids = $if_conditional_scope->cond_referenced_var_ids;
|
|
|
|
$cond_assigned_var_ids = $if_conditional_scope->cond_assigned_var_ids;
|
|
|
|
} catch (\Psalm\Exception\ScopeAnalysisException $e) {
|
|
|
|
return false;
|
2016-10-22 19:23:18 +02:00
|
|
|
}
|
|
|
|
|
2018-08-24 22:48:14 +02:00
|
|
|
$mixed_var_ids = [];
|
2018-01-08 05:59:17 +01:00
|
|
|
|
|
|
|
foreach ($if_context->vars_in_scope as $var_id => $type) {
|
2019-10-17 07:09:21 +02:00
|
|
|
if ($type->hasMixed() && isset($context->vars_in_scope[$var_id])) {
|
2018-01-08 05:59:17 +01:00
|
|
|
$mixed_var_ids[] = $var_id;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-07 07:26:06 +02:00
|
|
|
$if_clauses = Algebra::getFormula(
|
2016-12-27 19:58:58 +01:00
|
|
|
$stmt->cond,
|
2017-01-07 20:35:07 +01:00
|
|
|
$context->self,
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer,
|
2018-11-06 03:57:36 +01:00
|
|
|
$codebase
|
2016-12-27 19:58:58 +01:00
|
|
|
);
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2018-01-08 07:38:25 +01:00
|
|
|
$if_clauses = array_values(
|
2018-03-15 21:40:22 +01:00
|
|
|
array_map(
|
|
|
|
/**
|
|
|
|
* @return Clause
|
|
|
|
*/
|
2018-01-08 07:38:25 +01:00
|
|
|
function (Clause $c) use ($mixed_var_ids) {
|
|
|
|
$keys = array_keys($c->possibilities);
|
|
|
|
|
2019-11-12 16:21:27 +01:00
|
|
|
$mixed_var_ids = \array_diff($mixed_var_ids, $keys);
|
2019-11-12 16:12:07 +01:00
|
|
|
|
2018-01-08 07:38:25 +01:00
|
|
|
foreach ($keys as $key) {
|
|
|
|
foreach ($mixed_var_ids as $mixed_var_id) {
|
2018-01-18 00:06:26 +01:00
|
|
|
if (preg_match('/^' . preg_quote($mixed_var_id, '/') . '(\[|-)/', $key)) {
|
2018-03-15 21:40:22 +01:00
|
|
|
return new Clause([], true);
|
2018-01-08 07:38:25 +01:00
|
|
|
}
|
2018-01-08 05:59:17 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-15 21:40:22 +01:00
|
|
|
return $c;
|
|
|
|
},
|
|
|
|
$if_clauses
|
2018-01-08 07:38:25 +01:00
|
|
|
)
|
2018-01-08 05:59:17 +01:00
|
|
|
);
|
|
|
|
|
2017-04-02 21:26:10 +02:00
|
|
|
// this will see whether any of the clauses in set A conflict with the clauses in set B
|
2018-11-06 03:57:36 +01:00
|
|
|
AlgebraAnalyzer::checkForParadox(
|
2017-11-29 04:17:03 +01:00
|
|
|
$context->clauses,
|
|
|
|
$if_clauses,
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer,
|
2017-11-29 04:17:03 +01:00
|
|
|
$stmt->cond,
|
|
|
|
$cond_assigned_var_ids
|
|
|
|
);
|
|
|
|
|
|
|
|
// if we have assignments in the if, we may have duplicate clauses
|
|
|
|
if ($cond_assigned_var_ids) {
|
2018-05-07 07:26:06 +02:00
|
|
|
$if_clauses = Algebra::simplifyCNF($if_clauses);
|
2017-11-29 04:17:03 +01:00
|
|
|
}
|
2017-04-02 21:26:10 +02:00
|
|
|
|
2018-05-07 07:26:06 +02:00
|
|
|
$if_context->clauses = Algebra::simplifyCNF(array_merge($context->clauses, $if_clauses));
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2017-11-28 06:46:41 +01:00
|
|
|
// define this before we alter local claues after reconciliation
|
|
|
|
$if_scope->reasonable_clauses = $if_context->clauses;
|
|
|
|
|
2019-01-08 15:57:14 +01:00
|
|
|
try {
|
|
|
|
$if_scope->negated_clauses = Algebra::negateFormula($if_clauses);
|
|
|
|
} catch (\Psalm\Exception\ComplicatedExpressionException $e) {
|
|
|
|
$if_scope->negated_clauses = [];
|
|
|
|
}
|
2016-12-27 19:58:58 +01:00
|
|
|
|
2018-05-07 07:26:06 +02:00
|
|
|
$if_scope->negated_types = Algebra::getTruthsFromFormula(
|
|
|
|
Algebra::simplifyCNF(
|
2018-05-06 02:52:10 +02:00
|
|
|
array_merge($context->clauses, $if_scope->negated_clauses)
|
|
|
|
)
|
|
|
|
);
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2018-05-07 23:40:16 +02:00
|
|
|
$reconcilable_if_types = Algebra::getTruthsFromFormula(
|
|
|
|
$if_context->clauses,
|
|
|
|
$cond_referenced_var_ids
|
|
|
|
);
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2019-10-17 03:55:11 +02:00
|
|
|
if (array_filter(
|
|
|
|
$context->clauses,
|
|
|
|
function ($clause) {
|
|
|
|
return !!$clause->possibilities;
|
|
|
|
}
|
|
|
|
)) {
|
2019-10-18 21:06:37 +02:00
|
|
|
$omit_keys = array_reduce(
|
|
|
|
$context->clauses,
|
|
|
|
/**
|
|
|
|
* @param array<string> $carry
|
|
|
|
* @return array<string>
|
|
|
|
*/
|
|
|
|
function ($carry, Clause $clause) {
|
|
|
|
return array_merge($carry, array_keys($clause->possibilities));
|
|
|
|
},
|
|
|
|
[]
|
|
|
|
);
|
|
|
|
|
|
|
|
$omit_keys = array_combine($omit_keys, $omit_keys);
|
|
|
|
$omit_keys = array_diff_key($omit_keys, Algebra::getTruthsFromFormula($context->clauses));
|
|
|
|
|
|
|
|
$cond_referenced_var_ids = array_diff_key(
|
2019-10-17 03:55:11 +02:00
|
|
|
$cond_referenced_var_ids,
|
2019-10-18 21:06:37 +02:00
|
|
|
$omit_keys
|
2019-10-17 03:55:11 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-10-22 19:23:18 +02:00
|
|
|
// if the if has an || in the conditional, we cannot easily reason about it
|
|
|
|
if ($reconcilable_if_types) {
|
2017-11-28 06:46:41 +01:00
|
|
|
$changed_var_ids = [];
|
2016-12-28 20:20:16 +01:00
|
|
|
|
2016-10-22 19:23:18 +02:00
|
|
|
$if_vars_in_scope_reconciled =
|
2017-12-29 16:55:41 +01:00
|
|
|
Reconciler::reconcileKeyedTypes(
|
2016-10-22 19:23:18 +02:00
|
|
|
$reconcilable_if_types,
|
|
|
|
$if_context->vars_in_scope,
|
2017-11-28 06:46:41 +01:00
|
|
|
$changed_var_ids,
|
|
|
|
$cond_referenced_var_ids,
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer,
|
2019-07-25 14:33:27 +02:00
|
|
|
$statements_analyzer->getTemplateTypeMap() ?: [],
|
2018-12-08 19:18:55 +01:00
|
|
|
$if_context->inside_loop,
|
2018-04-11 20:37:01 +02:00
|
|
|
$context->check_variables
|
|
|
|
? new CodeLocation(
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer->getSource(),
|
2019-02-27 20:02:02 +01:00
|
|
|
$stmt->cond instanceof PhpParser\Node\Expr\BooleanNot
|
|
|
|
? $stmt->cond->expr
|
|
|
|
: $stmt->cond,
|
2018-04-11 20:37:01 +02:00
|
|
|
$context->include_location
|
2018-11-14 19:44:20 +01:00
|
|
|
) : null
|
2016-10-22 19:23:18 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
$if_context->vars_in_scope = $if_vars_in_scope_reconciled;
|
2018-01-28 23:28:34 +01:00
|
|
|
|
|
|
|
foreach ($reconcilable_if_types as $var_id => $_) {
|
2017-12-02 19:32:20 +01:00
|
|
|
$if_context->vars_possibly_in_scope[$var_id] = true;
|
|
|
|
}
|
2017-11-28 06:46:41 +01:00
|
|
|
|
|
|
|
if ($changed_var_ids) {
|
|
|
|
$if_context->removeReconciledClauses($changed_var_ids);
|
|
|
|
}
|
2018-01-20 17:48:16 +01:00
|
|
|
|
|
|
|
$if_scope->if_cond_changed_var_ids = $changed_var_ids;
|
2016-10-22 19:23:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$old_if_context = clone $if_context;
|
2016-11-02 07:29:00 +01:00
|
|
|
$context->vars_possibly_in_scope = array_merge(
|
|
|
|
$if_context->vars_possibly_in_scope,
|
|
|
|
$context->vars_possibly_in_scope
|
|
|
|
);
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2017-11-24 18:17:28 +01:00
|
|
|
$context->referenced_var_ids = array_merge(
|
|
|
|
$if_context->referenced_var_ids,
|
|
|
|
$context->referenced_var_ids
|
|
|
|
);
|
2017-02-01 05:24:33 +01:00
|
|
|
|
2016-11-11 23:13:13 +01:00
|
|
|
$temp_else_context = clone $original_context;
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2017-11-28 06:46:41 +01:00
|
|
|
$changed_var_ids = [];
|
2016-12-28 20:20:16 +01:00
|
|
|
|
2017-11-28 06:46:41 +01:00
|
|
|
if ($if_scope->negated_types) {
|
2017-12-29 16:55:41 +01:00
|
|
|
$else_vars_reconciled = Reconciler::reconcileKeyedTypes(
|
2016-11-11 23:13:13 +01:00
|
|
|
$if_scope->negated_types,
|
|
|
|
$temp_else_context->vars_in_scope,
|
2017-11-28 06:46:41 +01:00
|
|
|
$changed_var_ids,
|
2018-02-07 21:20:47 +01:00
|
|
|
$stmt->else || $stmt->elseifs ? $cond_referenced_var_ids : [],
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer,
|
2019-07-25 14:33:27 +02:00
|
|
|
$statements_analyzer->getTemplateTypeMap() ?: [],
|
2018-12-08 19:18:55 +01:00
|
|
|
$context->inside_loop,
|
2018-04-11 20:37:01 +02:00
|
|
|
$context->check_variables
|
|
|
|
? new CodeLocation(
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer->getSource(),
|
2019-02-27 20:02:02 +01:00
|
|
|
$stmt->cond instanceof PhpParser\Node\Expr\BooleanNot
|
|
|
|
? $stmt->cond->expr
|
|
|
|
: $stmt->cond,
|
2018-04-11 20:37:01 +02:00
|
|
|
$context->include_location
|
2018-11-14 19:44:20 +01:00
|
|
|
) : null
|
2016-10-22 19:23:18 +02:00
|
|
|
);
|
|
|
|
|
2016-11-11 23:13:13 +01:00
|
|
|
$temp_else_context->vars_in_scope = $else_vars_reconciled;
|
2016-10-22 19:23:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// we calculate the vars redefined in a hypothetical else statement to determine
|
|
|
|
// which vars of the if we can safely change
|
2018-02-17 17:24:08 +01:00
|
|
|
$pre_assignment_else_redefined_vars = $temp_else_context->getRedefinedVars($context->vars_in_scope, true);
|
2016-11-11 23:13:13 +01:00
|
|
|
|
2018-01-28 23:28:34 +01:00
|
|
|
// this captures statements in the if conditional
|
|
|
|
if ($context->collect_references) {
|
2018-06-17 02:01:33 +02:00
|
|
|
foreach ($if_context->unreferenced_vars as $var_id => $locations) {
|
|
|
|
if (!isset($context->unreferenced_vars[$var_id])) {
|
|
|
|
if (isset($if_scope->new_unreferenced_vars[$var_id])) {
|
|
|
|
$if_scope->new_unreferenced_vars[$var_id] += $locations;
|
|
|
|
} else {
|
|
|
|
$if_scope->new_unreferenced_vars[$var_id] = $locations;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$new_locations = array_diff_key(
|
|
|
|
$locations,
|
|
|
|
$context->unreferenced_vars[$var_id]
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($new_locations) {
|
|
|
|
if (isset($if_scope->new_unreferenced_vars[$var_id])) {
|
|
|
|
$if_scope->new_unreferenced_vars[$var_id] += $locations;
|
|
|
|
} else {
|
|
|
|
$if_scope->new_unreferenced_vars[$var_id] = $locations;
|
|
|
|
}
|
|
|
|
}
|
2018-01-28 23:28:34 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-11 23:13:13 +01:00
|
|
|
// check the if
|
2017-12-10 22:22:45 +01:00
|
|
|
if (self::analyzeIfBlock(
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer,
|
2016-11-11 23:13:13 +01:00
|
|
|
$stmt,
|
|
|
|
$if_scope,
|
|
|
|
$if_context,
|
|
|
|
$old_if_context,
|
|
|
|
$context,
|
2018-06-17 03:54:44 +02:00
|
|
|
$pre_assignment_else_redefined_vars
|
2017-12-10 22:22:45 +01:00
|
|
|
) === false) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2019-08-27 04:16:06 +02:00
|
|
|
// check the else
|
|
|
|
$else_context = clone $original_context;
|
|
|
|
|
2016-11-11 23:13:13 +01:00
|
|
|
// check the elseifs
|
|
|
|
foreach ($stmt->elseifs as $elseif) {
|
2017-12-10 22:22:45 +01:00
|
|
|
if (self::analyzeElseIfBlock(
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer,
|
2016-11-11 23:13:13 +01:00
|
|
|
$elseif,
|
|
|
|
$if_scope,
|
2019-08-27 04:16:06 +02:00
|
|
|
$else_context,
|
2018-11-06 03:57:36 +01:00
|
|
|
$context,
|
2019-08-27 04:16:06 +02:00
|
|
|
$codebase,
|
|
|
|
$else_context->branch_point ?: (int) $stmt->getAttribute('startFilePos')
|
2017-12-10 22:22:45 +01:00
|
|
|
) === false) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-10-22 19:23:18 +02:00
|
|
|
}
|
|
|
|
|
2018-05-31 00:56:44 +02:00
|
|
|
if ($stmt->else) {
|
2018-11-06 03:57:36 +01:00
|
|
|
if ($codebase->alter_code) {
|
2018-01-21 22:24:20 +01:00
|
|
|
$else_context->branch_point =
|
2018-01-22 04:05:57 +01:00
|
|
|
$else_context->branch_point ?: (int) $stmt->getAttribute('startFilePos');
|
2018-01-21 22:24:20 +01:00
|
|
|
}
|
2018-05-31 00:56:44 +02:00
|
|
|
}
|
2018-01-21 22:24:20 +01:00
|
|
|
|
2018-05-31 00:56:44 +02:00
|
|
|
if (self::analyzeElseBlock(
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer,
|
2018-05-31 00:56:44 +02:00
|
|
|
$stmt->else,
|
|
|
|
$if_scope,
|
|
|
|
$else_context,
|
2018-06-17 03:54:44 +02:00
|
|
|
$context
|
2018-05-31 00:56:44 +02:00
|
|
|
) === false) {
|
|
|
|
return false;
|
|
|
|
}
|
2018-01-25 07:04:26 +01:00
|
|
|
|
2018-06-17 03:54:44 +02:00
|
|
|
if ($context->loop_scope) {
|
|
|
|
$context->loop_scope->final_actions = array_unique(
|
2017-12-03 00:28:18 +01:00
|
|
|
array_merge(
|
2018-06-17 03:54:44 +02:00
|
|
|
$context->loop_scope->final_actions,
|
2017-12-03 00:28:18 +01:00
|
|
|
$if_scope->final_actions
|
|
|
|
)
|
2016-11-11 23:13:13 +01:00
|
|
|
);
|
|
|
|
}
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2017-02-01 05:24:33 +01:00
|
|
|
$context->vars_possibly_in_scope = array_merge(
|
|
|
|
$context->vars_possibly_in_scope,
|
|
|
|
$if_scope->new_vars_possibly_in_scope
|
|
|
|
);
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2018-05-18 17:02:50 +02:00
|
|
|
$context->possibly_assigned_var_ids = array_merge(
|
|
|
|
$context->possibly_assigned_var_ids,
|
|
|
|
$if_scope->possibly_assigned_var_ids ?: []
|
|
|
|
);
|
|
|
|
|
2016-11-11 23:13:13 +01:00
|
|
|
// vars can only be defined/redefined if there was an else (defined in every block)
|
2018-05-31 00:56:44 +02:00
|
|
|
$context->assigned_var_ids = array_merge(
|
|
|
|
$context->assigned_var_ids,
|
|
|
|
$if_scope->assigned_var_ids ?: []
|
|
|
|
);
|
2018-01-28 23:28:34 +01:00
|
|
|
|
2018-05-31 00:56:44 +02:00
|
|
|
if ($if_scope->new_vars) {
|
|
|
|
$context->vars_in_scope = array_merge($context->vars_in_scope, $if_scope->new_vars);
|
|
|
|
}
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2018-05-31 00:56:44 +02:00
|
|
|
if ($if_scope->redefined_vars) {
|
|
|
|
foreach ($if_scope->redefined_vars as $var_id => $type) {
|
|
|
|
$context->vars_in_scope[$var_id] = $type;
|
|
|
|
$if_scope->updated_vars[$var_id] = true;
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2018-05-31 00:56:44 +02:00
|
|
|
if ($if_scope->reasonable_clauses) {
|
|
|
|
$if_scope->reasonable_clauses = Context::filterClauses(
|
|
|
|
$var_id,
|
|
|
|
$if_scope->reasonable_clauses,
|
|
|
|
isset($context->vars_in_scope[$var_id])
|
|
|
|
? $context->vars_in_scope[$var_id]
|
|
|
|
: null,
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer
|
2018-05-31 00:56:44 +02:00
|
|
|
);
|
2017-09-03 00:15:52 +02:00
|
|
|
}
|
|
|
|
}
|
2018-05-31 00:56:44 +02:00
|
|
|
}
|
2018-05-14 22:29:51 +02:00
|
|
|
|
2018-05-31 00:56:44 +02:00
|
|
|
if ($if_scope->possible_param_types) {
|
|
|
|
foreach ($if_scope->possible_param_types as $var => $type) {
|
|
|
|
$context->possible_param_types[$var] = $type;
|
2017-12-03 00:28:18 +01:00
|
|
|
}
|
2016-11-11 23:13:13 +01:00
|
|
|
}
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2018-05-31 00:56:44 +02:00
|
|
|
if ($if_scope->reasonable_clauses
|
|
|
|
&& (count($if_scope->reasonable_clauses) > 1 || !$if_scope->reasonable_clauses[0]->wedge)
|
|
|
|
) {
|
|
|
|
$context->clauses = Algebra::simplifyCNF(
|
|
|
|
array_merge(
|
|
|
|
$if_scope->reasonable_clauses,
|
|
|
|
$context->clauses
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-11-11 23:13:13 +01:00
|
|
|
if ($if_scope->possibly_redefined_vars) {
|
2018-04-25 03:02:07 +02:00
|
|
|
foreach ($if_scope->possibly_redefined_vars as $var_id => $type) {
|
2018-06-17 02:01:33 +02:00
|
|
|
if (isset($context->vars_in_scope[$var_id])
|
2018-04-25 03:02:07 +02:00
|
|
|
&& !$type->failed_reconciliation
|
|
|
|
&& !isset($if_scope->updated_vars[$var_id])
|
2017-03-13 23:06:56 +01:00
|
|
|
) {
|
2018-04-25 03:02:07 +02:00
|
|
|
$combined_type = Type::combineUnionTypes(
|
|
|
|
$context->vars_in_scope[$var_id],
|
2019-01-05 06:15:53 +01:00
|
|
|
$type,
|
|
|
|
$codebase
|
2018-04-25 03:02:07 +02:00
|
|
|
);
|
2018-05-14 22:29:51 +02:00
|
|
|
|
|
|
|
if ($combined_type->equals($context->vars_in_scope[$var_id])) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-04-25 03:02:07 +02:00
|
|
|
$context->removeDescendents($var_id, $combined_type);
|
|
|
|
$context->vars_in_scope[$var_id] = $combined_type;
|
2016-10-22 19:23:18 +02:00
|
|
|
}
|
2016-11-11 23:13:13 +01:00
|
|
|
}
|
|
|
|
}
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2018-01-25 07:04:26 +01:00
|
|
|
if ($context->collect_references) {
|
2018-06-17 02:01:33 +02:00
|
|
|
foreach ($if_scope->new_unreferenced_vars as $var_id => $locations) {
|
2018-05-06 02:32:04 +02:00
|
|
|
if (($stmt->else
|
|
|
|
&& (isset($if_scope->assigned_var_ids[$var_id]) || isset($if_scope->new_vars[$var_id])))
|
|
|
|
|| !isset($context->vars_in_scope[$var_id])
|
2018-01-28 23:28:34 +01:00
|
|
|
) {
|
2018-06-17 02:01:33 +02:00
|
|
|
$context->unreferenced_vars[$var_id] = $locations;
|
2019-05-13 02:49:37 +02:00
|
|
|
} elseif (isset($if_scope->possibly_assigned_var_ids[$var_id])
|
|
|
|
|| isset($if_context->possibly_assigned_var_ids[$var_id])
|
|
|
|
) {
|
2018-06-17 02:01:33 +02:00
|
|
|
if (!isset($context->unreferenced_vars[$var_id])) {
|
|
|
|
$context->unreferenced_vars[$var_id] = $locations;
|
|
|
|
} else {
|
|
|
|
$context->unreferenced_vars[$var_id] += $locations;
|
|
|
|
}
|
|
|
|
} else {
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer->registerVariableUses($locations);
|
2018-01-28 23:28:34 +01:00
|
|
|
}
|
|
|
|
}
|
2018-06-17 02:01:33 +02:00
|
|
|
|
|
|
|
$context->possibly_assigned_var_ids += $if_scope->possibly_assigned_var_ids;
|
2018-01-25 07:04:26 +01:00
|
|
|
}
|
|
|
|
|
2016-11-11 23:13:13 +01:00
|
|
|
return null;
|
|
|
|
}
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2019-08-27 04:16:06 +02:00
|
|
|
/**
|
|
|
|
* @return \Psalm\Internal\Scope\IfConditionalScope
|
|
|
|
*/
|
|
|
|
private static function analyzeIfConditional(
|
|
|
|
StatementsAnalyzer $statements_analyzer,
|
2019-08-27 04:54:54 +02:00
|
|
|
PhpParser\Node\Expr $cond,
|
2019-08-27 04:16:06 +02:00
|
|
|
Context $context,
|
2019-08-27 04:54:54 +02:00
|
|
|
Codebase $codebase,
|
|
|
|
IfScope $if_scope,
|
|
|
|
?int $branch_point
|
2019-08-27 04:16:06 +02:00
|
|
|
) {
|
|
|
|
// get the first expression in the if, which should be evaluated on its own
|
|
|
|
// this allows us to update the context of $matches in
|
|
|
|
// if (!preg_match('/a/', 'aa', $matches)) {
|
|
|
|
// exit
|
|
|
|
// }
|
|
|
|
// echo $matches[0];
|
2019-08-27 04:54:54 +02:00
|
|
|
$first_if_cond_expr = self::getDefinitelyEvaluatedExpression($cond);
|
|
|
|
|
|
|
|
$entry_clauses = [];
|
|
|
|
|
|
|
|
if ($if_scope->negated_clauses) {
|
|
|
|
$entry_clauses = array_merge($context->clauses, $if_scope->negated_clauses);
|
|
|
|
|
|
|
|
$changed_var_ids = [];
|
|
|
|
|
|
|
|
if ($if_scope->negated_types) {
|
|
|
|
$vars_reconciled = Reconciler::reconcileKeyedTypes(
|
|
|
|
$if_scope->negated_types,
|
|
|
|
$context->vars_in_scope,
|
|
|
|
$changed_var_ids,
|
|
|
|
[],
|
|
|
|
$statements_analyzer,
|
|
|
|
[],
|
|
|
|
$context->inside_loop,
|
|
|
|
new CodeLocation(
|
|
|
|
$statements_analyzer->getSource(),
|
|
|
|
$cond instanceof PhpParser\Node\Expr\BooleanNot
|
|
|
|
? $cond->expr
|
|
|
|
: $cond,
|
|
|
|
$context->include_location,
|
|
|
|
false
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($changed_var_ids) {
|
|
|
|
$context = clone $context;
|
|
|
|
$context->vars_in_scope = $vars_reconciled;
|
|
|
|
|
2019-10-17 03:55:11 +02:00
|
|
|
$entry_clauses = array_values(
|
|
|
|
array_filter(
|
|
|
|
$entry_clauses,
|
|
|
|
/** @return bool */
|
|
|
|
function (Clause $c) use ($changed_var_ids) {
|
|
|
|
return count($c->possibilities) > 1
|
|
|
|
|| $c->wedge
|
|
|
|
|| !in_array(array_keys($c->possibilities)[0], $changed_var_ids, true);
|
|
|
|
}
|
|
|
|
)
|
2019-08-27 04:54:54 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-08-27 04:16:06 +02:00
|
|
|
|
|
|
|
$context->inside_conditional = true;
|
|
|
|
|
|
|
|
$pre_condition_vars_in_scope = $context->vars_in_scope;
|
|
|
|
|
|
|
|
$referenced_var_ids = $context->referenced_var_ids;
|
|
|
|
$context->referenced_var_ids = [];
|
|
|
|
|
|
|
|
$pre_assigned_var_ids = $context->assigned_var_ids;
|
|
|
|
$context->assigned_var_ids = [];
|
|
|
|
|
|
|
|
if ($first_if_cond_expr) {
|
|
|
|
if (ExpressionAnalyzer::analyze($statements_analyzer, $first_if_cond_expr, $context) === false) {
|
|
|
|
throw new \Psalm\Exception\ScopeAnalysisException();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$first_cond_assigned_var_ids = $context->assigned_var_ids;
|
|
|
|
$context->assigned_var_ids = array_merge(
|
|
|
|
$pre_assigned_var_ids,
|
|
|
|
$first_cond_assigned_var_ids
|
|
|
|
);
|
|
|
|
|
|
|
|
$first_cond_referenced_var_ids = $context->referenced_var_ids;
|
|
|
|
$context->referenced_var_ids = array_merge(
|
|
|
|
$referenced_var_ids,
|
|
|
|
$first_cond_referenced_var_ids
|
|
|
|
);
|
|
|
|
|
|
|
|
$context->inside_conditional = false;
|
|
|
|
|
|
|
|
$if_context = clone $context;
|
|
|
|
|
|
|
|
if ($codebase->alter_code) {
|
2019-08-27 04:54:54 +02:00
|
|
|
$if_context->branch_point = $branch_point;
|
2019-08-27 04:16:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// we need to clone the current context so our ongoing updates to $context don't mess with elseif/else blocks
|
|
|
|
$original_context = clone $context;
|
|
|
|
|
|
|
|
$if_context->inside_conditional = true;
|
|
|
|
|
2019-08-27 04:54:54 +02:00
|
|
|
if ($first_if_cond_expr !== $cond) {
|
2019-08-27 04:16:06 +02:00
|
|
|
$assigned_var_ids = $context->assigned_var_ids;
|
|
|
|
$if_context->assigned_var_ids = [];
|
|
|
|
|
|
|
|
$referenced_var_ids = $context->referenced_var_ids;
|
|
|
|
$if_context->referenced_var_ids = [];
|
|
|
|
|
2019-08-27 04:54:54 +02:00
|
|
|
if (ExpressionAnalyzer::analyze($statements_analyzer, $cond, $if_context) === false) {
|
2019-08-27 04:16:06 +02:00
|
|
|
throw new \Psalm\Exception\ScopeAnalysisException();
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @var array<string, bool> */
|
|
|
|
$more_cond_referenced_var_ids = $if_context->referenced_var_ids;
|
|
|
|
$if_context->referenced_var_ids = array_merge(
|
|
|
|
$more_cond_referenced_var_ids,
|
|
|
|
$referenced_var_ids
|
|
|
|
);
|
|
|
|
|
|
|
|
$cond_referenced_var_ids = array_merge(
|
|
|
|
$first_cond_referenced_var_ids,
|
|
|
|
$more_cond_referenced_var_ids
|
|
|
|
);
|
|
|
|
|
|
|
|
/** @var array<string, bool> */
|
|
|
|
$more_cond_assigned_var_ids = $if_context->assigned_var_ids;
|
|
|
|
$if_context->assigned_var_ids = array_merge(
|
|
|
|
$more_cond_assigned_var_ids,
|
|
|
|
$assigned_var_ids
|
|
|
|
);
|
|
|
|
|
|
|
|
$cond_assigned_var_ids = array_merge(
|
|
|
|
$first_cond_assigned_var_ids,
|
|
|
|
$more_cond_assigned_var_ids
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
$cond_referenced_var_ids = $first_cond_referenced_var_ids;
|
|
|
|
|
|
|
|
$cond_assigned_var_ids = $first_cond_assigned_var_ids;
|
|
|
|
}
|
|
|
|
|
|
|
|
$newish_var_ids = array_map(
|
|
|
|
/**
|
|
|
|
* @param Type\Union $_
|
|
|
|
*
|
|
|
|
* @return true
|
|
|
|
*/
|
|
|
|
function (Type\Union $_) {
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
array_diff_key(
|
|
|
|
$if_context->vars_in_scope,
|
|
|
|
$pre_condition_vars_in_scope,
|
|
|
|
$cond_referenced_var_ids,
|
|
|
|
$cond_assigned_var_ids
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
// get all the var ids that were referened in the conditional, but not assigned in it
|
|
|
|
$cond_referenced_var_ids = array_diff_key($cond_referenced_var_ids, $cond_assigned_var_ids);
|
|
|
|
|
|
|
|
// remove all newly-asserted var ids too
|
|
|
|
$cond_referenced_var_ids = array_filter(
|
|
|
|
$cond_referenced_var_ids,
|
|
|
|
/**
|
|
|
|
* @param string $var_id
|
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
function ($var_id) use ($pre_condition_vars_in_scope) {
|
|
|
|
return isset($pre_condition_vars_in_scope[$var_id]);
|
|
|
|
},
|
|
|
|
ARRAY_FILTER_USE_KEY
|
|
|
|
);
|
|
|
|
|
|
|
|
$cond_referenced_var_ids = array_merge($newish_var_ids, $cond_referenced_var_ids);
|
|
|
|
|
|
|
|
$if_context->inside_conditional = false;
|
|
|
|
|
|
|
|
return new \Psalm\Internal\Scope\IfConditionalScope(
|
|
|
|
$if_context,
|
|
|
|
$original_context,
|
|
|
|
$cond_referenced_var_ids,
|
2019-08-27 04:54:54 +02:00
|
|
|
$cond_assigned_var_ids,
|
|
|
|
$entry_clauses
|
2019-08-27 04:16:06 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-11-11 23:13:13 +01:00
|
|
|
/**
|
2018-11-11 18:01:14 +01:00
|
|
|
* @param StatementsAnalyzer $statements_analyzer
|
2016-11-11 23:13:13 +01:00
|
|
|
* @param PhpParser\Node\Stmt\If_ $stmt
|
|
|
|
* @param IfScope $if_scope
|
|
|
|
* @param Context $if_context
|
|
|
|
* @param Context $old_if_context
|
|
|
|
* @param Context $outer_context
|
|
|
|
* @param array<string,Type\Union> $pre_assignment_else_redefined_vars
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-11-11 23:13:13 +01:00
|
|
|
* @return false|null
|
|
|
|
*/
|
2017-01-07 21:09:47 +01:00
|
|
|
protected static function analyzeIfBlock(
|
2018-11-11 18:01:14 +01:00
|
|
|
StatementsAnalyzer $statements_analyzer,
|
2016-11-11 23:13:13 +01:00
|
|
|
PhpParser\Node\Stmt\If_ $stmt,
|
|
|
|
IfScope $if_scope,
|
|
|
|
Context $if_context,
|
|
|
|
Context $old_if_context,
|
|
|
|
Context $outer_context,
|
2018-06-17 03:54:44 +02:00
|
|
|
array $pre_assignment_else_redefined_vars
|
2016-11-11 23:13:13 +01:00
|
|
|
) {
|
2018-11-11 18:01:14 +01:00
|
|
|
$codebase = $statements_analyzer->getCodebase();
|
2018-07-13 05:26:08 +02:00
|
|
|
|
2019-01-03 00:14:47 +01:00
|
|
|
$if_context->parent_context = $outer_context;
|
|
|
|
|
|
|
|
$assigned_var_ids = $if_context->assigned_var_ids;
|
|
|
|
$possibly_assigned_var_ids = $if_context->possibly_assigned_var_ids;
|
|
|
|
$if_context->assigned_var_ids = [];
|
|
|
|
$if_context->possibly_assigned_var_ids = [];
|
|
|
|
|
|
|
|
if ($statements_analyzer->analyze(
|
|
|
|
$stmt->stmts,
|
|
|
|
$if_context
|
|
|
|
) === false
|
|
|
|
) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
$final_actions = ScopeAnalyzer::getFinalControlActions(
|
2018-07-13 05:26:08 +02:00
|
|
|
$stmt->stmts,
|
2018-11-06 03:57:36 +01:00
|
|
|
$codebase->config->exit_functions,
|
2018-07-13 05:26:08 +02:00
|
|
|
$outer_context->inside_case
|
|
|
|
);
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
$has_ending_statements = $final_actions === [ScopeAnalyzer::ACTION_END];
|
2017-11-28 06:25:21 +01:00
|
|
|
|
|
|
|
$has_leaving_statements = $has_ending_statements
|
2018-11-06 03:57:36 +01:00
|
|
|
|| (count($final_actions) && !in_array(ScopeAnalyzer::ACTION_NONE, $final_actions, true));
|
2016-11-11 23:13:13 +01:00
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
$has_break_statement = $final_actions === [ScopeAnalyzer::ACTION_BREAK];
|
|
|
|
$has_continue_statement = $final_actions === [ScopeAnalyzer::ACTION_CONTINUE];
|
|
|
|
$has_leave_switch_statement = $final_actions === [ScopeAnalyzer::ACTION_LEAVE_SWITCH];
|
2017-12-03 00:28:18 +01:00
|
|
|
|
|
|
|
$if_scope->final_actions = $final_actions;
|
|
|
|
|
2017-11-28 06:46:41 +01:00
|
|
|
/** @var array<string, bool> */
|
|
|
|
$new_assigned_var_ids = $if_context->assigned_var_ids;
|
2018-05-18 17:02:50 +02:00
|
|
|
/** @var array<string, bool> */
|
|
|
|
$new_possibly_assigned_var_ids = $if_context->possibly_assigned_var_ids;
|
|
|
|
|
|
|
|
$if_context->assigned_var_ids = array_merge($assigned_var_ids, $new_assigned_var_ids);
|
|
|
|
$if_context->possibly_assigned_var_ids = array_merge(
|
|
|
|
$possibly_assigned_var_ids,
|
|
|
|
$new_possibly_assigned_var_ids
|
|
|
|
);
|
2017-11-28 06:46:41 +01:00
|
|
|
|
2017-02-23 06:25:28 +01:00
|
|
|
if ($if_context->byref_constraints !== null) {
|
|
|
|
foreach ($if_context->byref_constraints as $var_id => $byref_constraint) {
|
2018-01-25 07:04:26 +01:00
|
|
|
if ($outer_context->byref_constraints !== null
|
|
|
|
&& isset($outer_context->byref_constraints[$var_id])
|
|
|
|
&& $byref_constraint->type
|
|
|
|
&& ($outer_constraint_type = $outer_context->byref_constraints[$var_id]->type)
|
2018-11-06 03:57:36 +01:00
|
|
|
&& !TypeAnalyzer::isContainedBy(
|
|
|
|
$codebase,
|
2017-02-23 06:25:28 +01:00
|
|
|
$byref_constraint->type,
|
2018-01-25 07:04:26 +01:00
|
|
|
$outer_constraint_type
|
2017-02-23 06:25:28 +01:00
|
|
|
)
|
|
|
|
) {
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new ConflictingReferenceConstraint(
|
2018-02-19 19:06:35 +01:00
|
|
|
'There is more than one pass-by-reference constraint on ' . $var_id,
|
2018-11-11 18:01:14 +01:00
|
|
|
new CodeLocation($statements_analyzer, $stmt, $outer_context->include_location, true)
|
2017-02-23 06:25:28 +01:00
|
|
|
),
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer->getSuppressedIssues()
|
2017-02-23 06:25:28 +01:00
|
|
|
)) {
|
|
|
|
// fall through
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$outer_context->byref_constraints[$var_id] = $byref_constraint;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-27 05:09:18 +01:00
|
|
|
if ($outer_context->collect_references) {
|
2017-11-24 18:17:28 +01:00
|
|
|
$outer_context->referenced_var_ids = array_merge(
|
|
|
|
$outer_context->referenced_var_ids,
|
|
|
|
$if_context->referenced_var_ids
|
2017-02-01 05:24:33 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-11-11 23:13:13 +01:00
|
|
|
$mic_drop = false;
|
2016-11-02 07:29:00 +01:00
|
|
|
|
2016-11-11 23:13:13 +01:00
|
|
|
if (!$has_leaving_statements) {
|
|
|
|
$if_scope->new_vars = array_diff_key($if_context->vars_in_scope, $outer_context->vars_in_scope);
|
|
|
|
|
2017-12-03 00:28:18 +01:00
|
|
|
$if_scope->redefined_vars = $if_context->getRedefinedVars($outer_context->vars_in_scope);
|
2016-11-11 23:13:13 +01:00
|
|
|
$if_scope->possibly_redefined_vars = $if_scope->redefined_vars;
|
2018-01-28 23:28:34 +01:00
|
|
|
$if_scope->assigned_var_ids = $new_assigned_var_ids;
|
2018-05-18 17:02:50 +02:00
|
|
|
$if_scope->possibly_assigned_var_ids = $new_possibly_assigned_var_ids;
|
2017-11-28 06:46:41 +01:00
|
|
|
|
|
|
|
$changed_var_ids = array_keys($new_assigned_var_ids);
|
|
|
|
|
2018-01-20 17:48:16 +01:00
|
|
|
// if the variable was only set in the conditional, it's not possibly redefined
|
|
|
|
foreach ($if_scope->possibly_redefined_vars as $var_id => $_) {
|
2018-05-18 17:02:50 +02:00
|
|
|
if (!isset($new_possibly_assigned_var_ids[$var_id])
|
2018-01-20 17:48:16 +01:00
|
|
|
&& in_array($var_id, $if_scope->if_cond_changed_var_ids, true)
|
|
|
|
) {
|
|
|
|
unset($if_scope->possibly_redefined_vars[$var_id]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-28 06:46:41 +01:00
|
|
|
if ($if_scope->reasonable_clauses) {
|
|
|
|
// remove all reasonable clauses that would be negated by the if stmts
|
|
|
|
foreach ($changed_var_ids as $var_id) {
|
|
|
|
$if_scope->reasonable_clauses = Context::filterClauses(
|
|
|
|
$var_id,
|
|
|
|
$if_scope->reasonable_clauses,
|
|
|
|
isset($if_context->vars_in_scope[$var_id]) ? $if_context->vars_in_scope[$var_id] : null,
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer
|
2017-11-28 06:46:41 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2017-12-03 00:28:18 +01:00
|
|
|
if (!$has_break_statement) {
|
|
|
|
$if_scope->reasonable_clauses = [];
|
|
|
|
}
|
2017-11-28 06:46:41 +01:00
|
|
|
}
|
|
|
|
|
2017-12-03 00:28:18 +01:00
|
|
|
if ($has_leaving_statements && !$has_break_statement && !$stmt->else && !$stmt->elseifs) {
|
2017-03-16 16:46:07 +01:00
|
|
|
if ($if_scope->negated_types) {
|
2017-11-28 06:46:41 +01:00
|
|
|
$changed_var_ids = [];
|
2016-12-28 20:20:16 +01:00
|
|
|
|
2017-12-29 16:55:41 +01:00
|
|
|
$outer_context_vars_reconciled = Reconciler::reconcileKeyedTypes(
|
2017-03-16 16:46:07 +01:00
|
|
|
$if_scope->negated_types,
|
|
|
|
$outer_context->vars_in_scope,
|
2017-11-28 06:46:41 +01:00
|
|
|
$changed_var_ids,
|
|
|
|
[],
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer,
|
2019-07-25 14:33:27 +02:00
|
|
|
$statements_analyzer->getTemplateTypeMap() ?: [],
|
2018-12-08 19:18:55 +01:00
|
|
|
$outer_context->inside_loop,
|
2017-06-21 20:22:52 +02:00
|
|
|
new CodeLocation(
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer->getSource(),
|
2019-02-27 20:02:02 +01:00
|
|
|
$stmt->cond instanceof PhpParser\Node\Expr\BooleanNot
|
|
|
|
? $stmt->cond->expr
|
|
|
|
: $stmt->cond,
|
2017-06-21 20:22:52 +02:00
|
|
|
$outer_context->include_location,
|
|
|
|
false
|
2018-11-14 19:44:20 +01:00
|
|
|
)
|
2017-03-16 16:46:07 +01:00
|
|
|
);
|
2016-11-02 07:29:00 +01:00
|
|
|
|
2017-11-28 06:46:41 +01:00
|
|
|
foreach ($changed_var_ids as $changed_var_id) {
|
|
|
|
$outer_context->removeVarFromConflictingClauses($changed_var_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
$changed_var_ids = array_unique(
|
|
|
|
array_merge(
|
|
|
|
$changed_var_ids,
|
|
|
|
array_keys($new_assigned_var_ids)
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
foreach ($changed_var_ids as $var_id) {
|
|
|
|
$if_scope->negated_clauses = Context::filterClauses(
|
|
|
|
$var_id,
|
|
|
|
$if_scope->negated_clauses
|
|
|
|
);
|
2017-03-16 16:46:07 +01:00
|
|
|
}
|
2016-12-29 00:55:16 +01:00
|
|
|
|
2017-03-16 16:46:07 +01:00
|
|
|
$outer_context->vars_in_scope = $outer_context_vars_reconciled;
|
|
|
|
$mic_drop = true;
|
2016-10-22 19:23:18 +02:00
|
|
|
}
|
|
|
|
|
2018-05-07 07:26:06 +02:00
|
|
|
$outer_context->clauses = Algebra::simplifyCNF(
|
2017-03-16 19:45:45 +01:00
|
|
|
array_merge($outer_context->clauses, $if_scope->negated_clauses)
|
2017-03-16 16:46:07 +01:00
|
|
|
);
|
2016-11-11 23:13:13 +01:00
|
|
|
}
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2016-11-11 23:13:13 +01:00
|
|
|
// update the parent context as necessary, but only if we can safely reason about type negation.
|
|
|
|
// We only update vars that changed both at the start of the if block and then again by an assignment
|
|
|
|
// in the if statement.
|
2016-12-27 19:58:58 +01:00
|
|
|
if ($if_scope->negated_types && !$mic_drop) {
|
2016-12-29 00:55:16 +01:00
|
|
|
$vars_to_update = array_intersect(
|
|
|
|
array_keys($pre_assignment_else_redefined_vars),
|
|
|
|
array_keys($if_scope->negated_types)
|
|
|
|
);
|
|
|
|
|
2017-11-20 05:25:14 +01:00
|
|
|
$extra_vars_to_update = [];
|
|
|
|
|
|
|
|
// if there's an object-like array in there, we also need to update the root array variable
|
|
|
|
foreach ($vars_to_update as $var_id) {
|
|
|
|
$bracked_pos = strpos($var_id, '[');
|
|
|
|
if ($bracked_pos !== false) {
|
|
|
|
$extra_vars_to_update[] = substr($var_id, 0, $bracked_pos);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($extra_vars_to_update) {
|
|
|
|
$vars_to_update = array_unique(array_merge($extra_vars_to_update, $vars_to_update));
|
|
|
|
}
|
|
|
|
|
2017-12-03 22:25:52 +01:00
|
|
|
//update $if_context vars to include the pre-assignment else vars
|
|
|
|
if (!$stmt->else && !$has_leaving_statements) {
|
|
|
|
foreach ($pre_assignment_else_redefined_vars as $var_id => $type) {
|
|
|
|
if (isset($if_context->vars_in_scope[$var_id])) {
|
|
|
|
$if_context->vars_in_scope[$var_id] = Type::combineUnionTypes(
|
|
|
|
$if_context->vars_in_scope[$var_id],
|
2019-10-08 19:07:28 +02:00
|
|
|
$type,
|
|
|
|
$codebase
|
2017-12-03 22:25:52 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-11 23:13:13 +01:00
|
|
|
$outer_context->update(
|
|
|
|
$old_if_context,
|
|
|
|
$if_context,
|
|
|
|
$has_leaving_statements,
|
2016-12-29 00:55:16 +01:00
|
|
|
$vars_to_update,
|
2016-11-11 23:13:13 +01:00
|
|
|
$if_scope->updated_vars
|
2016-11-02 07:29:00 +01:00
|
|
|
);
|
2016-11-11 23:13:13 +01:00
|
|
|
}
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2016-11-11 23:13:13 +01:00
|
|
|
if (!$has_ending_statements) {
|
2018-05-18 17:02:50 +02:00
|
|
|
$vars_possibly_in_scope = array_diff_key(
|
|
|
|
$if_context->vars_possibly_in_scope,
|
|
|
|
$outer_context->vars_possibly_in_scope
|
|
|
|
);
|
|
|
|
|
2018-06-17 03:54:44 +02:00
|
|
|
if ($if_context->loop_scope) {
|
2017-12-03 00:28:18 +01:00
|
|
|
if (!$has_continue_statement && !$has_break_statement) {
|
2018-05-18 17:02:50 +02:00
|
|
|
$if_scope->new_vars_possibly_in_scope = $vars_possibly_in_scope;
|
2016-10-22 19:23:18 +02:00
|
|
|
}
|
2017-12-03 00:28:18 +01:00
|
|
|
|
2018-06-17 03:54:44 +02:00
|
|
|
$if_context->loop_scope->vars_possibly_in_scope = array_merge(
|
2018-05-18 17:02:50 +02:00
|
|
|
$vars_possibly_in_scope,
|
2018-06-17 03:54:44 +02:00
|
|
|
$if_context->loop_scope->vars_possibly_in_scope
|
2017-12-03 00:28:18 +01:00
|
|
|
);
|
|
|
|
} elseif (!$has_leaving_statements) {
|
2018-05-18 17:02:50 +02:00
|
|
|
$if_scope->new_vars_possibly_in_scope = $vars_possibly_in_scope;
|
2016-10-22 19:23:18 +02:00
|
|
|
}
|
2018-06-17 02:01:33 +02:00
|
|
|
|
|
|
|
if ($if_context->collect_references && (!$has_leaving_statements || $has_leave_switch_statement)) {
|
|
|
|
foreach ($if_context->unreferenced_vars as $var_id => $locations) {
|
|
|
|
if (!isset($outer_context->unreferenced_vars[$var_id])) {
|
|
|
|
if (isset($if_scope->new_unreferenced_vars[$var_id])) {
|
|
|
|
$if_scope->new_unreferenced_vars[$var_id] += $locations;
|
|
|
|
} else {
|
|
|
|
$if_scope->new_unreferenced_vars[$var_id] = $locations;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$new_locations = array_diff_key(
|
|
|
|
$locations,
|
|
|
|
$outer_context->unreferenced_vars[$var_id]
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($new_locations) {
|
|
|
|
if (isset($if_scope->new_unreferenced_vars[$var_id])) {
|
|
|
|
$if_scope->new_unreferenced_vars[$var_id] += $locations;
|
|
|
|
} else {
|
|
|
|
$if_scope->new_unreferenced_vars[$var_id] = $locations;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-11-11 23:13:13 +01:00
|
|
|
}
|
2018-06-22 07:13:49 +02:00
|
|
|
|
|
|
|
if ($outer_context->collect_exceptions) {
|
2019-03-29 00:43:14 +01:00
|
|
|
$outer_context->mergeExceptions($if_context);
|
2018-06-22 07:13:49 +02:00
|
|
|
}
|
2016-11-11 23:13:13 +01:00
|
|
|
}
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2016-11-11 23:13:13 +01:00
|
|
|
/**
|
2018-11-11 18:01:14 +01:00
|
|
|
* @param StatementsAnalyzer $statements_analyzer
|
2016-11-11 23:13:13 +01:00
|
|
|
* @param PhpParser\Node\Stmt\ElseIf_ $elseif
|
|
|
|
* @param IfScope $if_scope
|
|
|
|
* @param Context $elseif_context
|
|
|
|
* @param Context $outer_context
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-11-11 23:13:13 +01:00
|
|
|
* @return false|null
|
|
|
|
*/
|
2017-01-07 21:09:47 +01:00
|
|
|
protected static function analyzeElseIfBlock(
|
2018-11-11 18:01:14 +01:00
|
|
|
StatementsAnalyzer $statements_analyzer,
|
2016-11-11 23:13:13 +01:00
|
|
|
PhpParser\Node\Stmt\ElseIf_ $elseif,
|
|
|
|
IfScope $if_scope,
|
2019-08-27 04:16:06 +02:00
|
|
|
Context $else_context,
|
2018-11-06 03:57:36 +01:00
|
|
|
Context $outer_context,
|
2019-08-27 04:16:06 +02:00
|
|
|
Codebase $codebase,
|
|
|
|
?int $branch_point
|
2016-11-11 23:13:13 +01:00
|
|
|
) {
|
2019-08-27 04:54:54 +02:00
|
|
|
$pre_conditional_context = clone $else_context;
|
2019-08-27 04:16:06 +02:00
|
|
|
|
2019-08-27 04:54:54 +02:00
|
|
|
try {
|
|
|
|
$if_conditional_scope = self::analyzeIfConditional(
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer,
|
2019-08-27 04:54:54 +02:00
|
|
|
$elseif->cond,
|
|
|
|
$else_context,
|
|
|
|
$codebase,
|
|
|
|
$if_scope,
|
|
|
|
$branch_point
|
2016-11-11 23:13:13 +01:00
|
|
|
);
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2019-08-27 04:54:54 +02:00
|
|
|
$elseif_context = $if_conditional_scope->if_context;
|
|
|
|
$original_context = $if_conditional_scope->original_context;
|
|
|
|
$cond_referenced_var_ids = $if_conditional_scope->cond_referenced_var_ids;
|
|
|
|
$cond_assigned_var_ids = $if_conditional_scope->cond_assigned_var_ids;
|
|
|
|
$entry_clauses = $if_conditional_scope->entry_clauses;
|
|
|
|
} catch (\Psalm\Exception\ScopeAnalysisException $e) {
|
2016-12-09 18:06:14 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-08-24 22:48:14 +02:00
|
|
|
$mixed_var_ids = [];
|
2018-01-08 05:59:17 +01:00
|
|
|
|
|
|
|
foreach ($elseif_context->vars_in_scope as $var_id => $type) {
|
2018-12-08 19:18:55 +01:00
|
|
|
if ($type->hasMixed()) {
|
2018-01-08 05:59:17 +01:00
|
|
|
$mixed_var_ids[] = $var_id;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-07 07:26:06 +02:00
|
|
|
$elseif_clauses = Algebra::getFormula(
|
2016-12-27 19:58:58 +01:00
|
|
|
$elseif->cond,
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer->getFQCLN(),
|
|
|
|
$statements_analyzer,
|
2018-11-06 03:57:36 +01:00
|
|
|
$codebase
|
2016-12-27 19:58:58 +01:00
|
|
|
);
|
|
|
|
|
2018-04-19 17:58:51 +02:00
|
|
|
$elseif_clauses = array_map(
|
|
|
|
/**
|
|
|
|
* @return Clause
|
|
|
|
*/
|
|
|
|
function (Clause $c) use ($mixed_var_ids) {
|
|
|
|
$keys = array_keys($c->possibilities);
|
2018-01-08 07:38:25 +01:00
|
|
|
|
2019-11-12 16:21:27 +01:00
|
|
|
$mixed_var_ids = \array_diff($mixed_var_ids, $keys);
|
2019-11-12 16:12:07 +01:00
|
|
|
|
2018-04-19 17:58:51 +02:00
|
|
|
foreach ($keys as $key) {
|
|
|
|
foreach ($mixed_var_ids as $mixed_var_id) {
|
|
|
|
if (preg_match('/^' . preg_quote($mixed_var_id, '/') . '(\[|-)/', $key)) {
|
|
|
|
return new Clause([], true);
|
2018-01-08 05:59:17 +01:00
|
|
|
}
|
|
|
|
}
|
2018-01-08 07:38:25 +01:00
|
|
|
}
|
2018-04-19 17:58:51 +02:00
|
|
|
|
|
|
|
return $c;
|
|
|
|
},
|
|
|
|
$elseif_clauses
|
2018-01-08 05:59:17 +01:00
|
|
|
);
|
|
|
|
|
2018-04-19 17:58:51 +02:00
|
|
|
$entry_clauses = array_map(
|
|
|
|
/**
|
|
|
|
* @return Clause
|
|
|
|
*/
|
2019-08-27 04:54:54 +02:00
|
|
|
function (Clause $c) use ($cond_assigned_var_ids) {
|
2018-04-19 17:58:51 +02:00
|
|
|
$keys = array_keys($c->possibilities);
|
|
|
|
|
|
|
|
foreach ($keys as $key) {
|
2019-08-27 04:54:54 +02:00
|
|
|
foreach ($cond_assigned_var_ids as $conditional_assigned_var_id => $_) {
|
2018-04-19 17:58:51 +02:00
|
|
|
if (preg_match('/^' . preg_quote($conditional_assigned_var_id, '/') . '(\[|-|$)/', $key)) {
|
|
|
|
return new Clause([], true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $c;
|
|
|
|
},
|
|
|
|
$entry_clauses
|
|
|
|
);
|
2018-01-08 07:38:25 +01:00
|
|
|
|
2017-04-02 21:26:10 +02:00
|
|
|
// this will see whether any of the clauses in set A conflict with the clauses in set B
|
2018-11-06 03:57:36 +01:00
|
|
|
AlgebraAnalyzer::checkForParadox(
|
2017-11-29 04:17:03 +01:00
|
|
|
$entry_clauses,
|
|
|
|
$elseif_clauses,
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer,
|
2017-11-29 04:17:03 +01:00
|
|
|
$elseif->cond,
|
2019-08-27 04:54:54 +02:00
|
|
|
$cond_assigned_var_ids
|
2017-11-29 04:17:03 +01:00
|
|
|
);
|
2017-04-02 21:26:10 +02:00
|
|
|
|
2018-05-07 07:26:06 +02:00
|
|
|
$elseif_context->clauses = Algebra::simplifyCNF(
|
2016-12-27 19:58:58 +01:00
|
|
|
array_merge(
|
2017-04-02 21:26:10 +02:00
|
|
|
$entry_clauses,
|
2016-12-27 19:58:58 +01:00
|
|
|
$elseif_clauses
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
2019-08-07 04:58:53 +02:00
|
|
|
try {
|
2019-10-17 03:55:11 +02:00
|
|
|
if (array_filter(
|
|
|
|
$entry_clauses,
|
|
|
|
function ($clause) {
|
|
|
|
return !!$clause->possibilities;
|
|
|
|
}
|
|
|
|
)) {
|
2019-10-18 21:06:37 +02:00
|
|
|
$omit_keys = array_reduce(
|
|
|
|
$entry_clauses,
|
|
|
|
/**
|
|
|
|
* @param array<string> $carry
|
|
|
|
* @return array<string>
|
|
|
|
*/
|
|
|
|
function ($carry, Clause $clause) {
|
|
|
|
return array_merge($carry, array_keys($clause->possibilities));
|
|
|
|
},
|
|
|
|
[]
|
|
|
|
);
|
|
|
|
|
|
|
|
$omit_keys = array_combine($omit_keys, $omit_keys);
|
|
|
|
$omit_keys = array_diff_key($omit_keys, Algebra::getTruthsFromFormula($entry_clauses));
|
|
|
|
|
|
|
|
$cond_referenced_var_ids = array_diff_key(
|
2019-10-17 03:55:11 +02:00
|
|
|
$cond_referenced_var_ids,
|
2019-10-18 21:06:37 +02:00
|
|
|
$omit_keys
|
2019-10-17 03:55:11 +02:00
|
|
|
);
|
|
|
|
}
|
2019-08-07 04:58:53 +02:00
|
|
|
$reconcilable_elseif_types = Algebra::getTruthsFromFormula($elseif_context->clauses);
|
|
|
|
$negated_elseif_types = Algebra::getTruthsFromFormula(Algebra::negateFormula($elseif_clauses));
|
|
|
|
} catch (\Psalm\Exception\ComplicatedExpressionException $e) {
|
|
|
|
$reconcilable_elseif_types = [];
|
|
|
|
$negated_elseif_types = [];
|
|
|
|
}
|
2016-11-11 23:13:13 +01:00
|
|
|
|
|
|
|
$all_negated_vars = array_unique(
|
|
|
|
array_merge(
|
|
|
|
array_keys($negated_elseif_types),
|
|
|
|
array_keys($if_scope->negated_types)
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
foreach ($all_negated_vars as $var_id) {
|
|
|
|
if (isset($negated_elseif_types[$var_id])) {
|
|
|
|
if (isset($if_scope->negated_types[$var_id])) {
|
2018-05-13 01:38:43 +02:00
|
|
|
$if_scope->negated_types[$var_id] = array_merge(
|
|
|
|
$if_scope->negated_types[$var_id],
|
|
|
|
$negated_elseif_types[$var_id]
|
|
|
|
);
|
2016-11-11 23:13:13 +01:00
|
|
|
} else {
|
|
|
|
$if_scope->negated_types[$var_id] = $negated_elseif_types[$var_id];
|
|
|
|
}
|
2016-10-22 19:23:18 +02:00
|
|
|
}
|
2016-11-11 23:13:13 +01:00
|
|
|
}
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2019-08-27 04:54:54 +02:00
|
|
|
$changed_var_ids = [];
|
2018-01-20 17:48:16 +01:00
|
|
|
|
2016-11-11 23:13:13 +01:00
|
|
|
// if the elseif has an || in the conditional, we cannot easily reason about it
|
|
|
|
if ($reconcilable_elseif_types) {
|
2017-12-29 16:55:41 +01:00
|
|
|
$elseif_vars_reconciled = Reconciler::reconcileKeyedTypes(
|
2016-11-11 23:13:13 +01:00
|
|
|
$reconcilable_elseif_types,
|
|
|
|
$elseif_context->vars_in_scope,
|
2017-11-28 06:46:41 +01:00
|
|
|
$changed_var_ids,
|
2019-08-27 04:54:54 +02:00
|
|
|
$cond_referenced_var_ids,
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer,
|
2019-07-25 14:33:27 +02:00
|
|
|
$statements_analyzer->getTemplateTypeMap() ?: [],
|
2018-12-08 19:18:55 +01:00
|
|
|
$elseif_context->inside_loop,
|
2019-02-27 20:02:02 +01:00
|
|
|
new CodeLocation(
|
|
|
|
$statements_analyzer->getSource(),
|
|
|
|
$elseif->cond instanceof PhpParser\Node\Expr\BooleanNot
|
|
|
|
? $elseif->cond->expr
|
|
|
|
: $elseif->cond,
|
|
|
|
$outer_context->include_location
|
|
|
|
)
|
2016-11-11 23:13:13 +01:00
|
|
|
);
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2016-11-11 23:13:13 +01:00
|
|
|
$elseif_context->vars_in_scope = $elseif_vars_reconciled;
|
2017-11-28 06:46:41 +01:00
|
|
|
|
|
|
|
if ($changed_var_ids) {
|
|
|
|
$elseif_context->removeReconciledClauses($changed_var_ids);
|
|
|
|
}
|
2016-11-11 23:13:13 +01:00
|
|
|
}
|
|
|
|
|
2018-01-20 17:48:16 +01:00
|
|
|
$pre_stmts_assigned_var_ids = $elseif_context->assigned_var_ids;
|
|
|
|
$elseif_context->assigned_var_ids = [];
|
2018-06-17 02:01:33 +02:00
|
|
|
$pre_stmts_possibly_assigned_var_ids = $elseif_context->possibly_assigned_var_ids;
|
|
|
|
$elseif_context->possibly_assigned_var_ids = [];
|
2018-01-20 17:48:16 +01:00
|
|
|
|
2018-11-11 18:01:14 +01:00
|
|
|
if ($statements_analyzer->analyze(
|
2018-01-03 03:23:48 +01:00
|
|
|
$elseif->stmts,
|
2018-06-17 03:54:44 +02:00
|
|
|
$elseif_context
|
2018-01-03 03:23:48 +01:00
|
|
|
) === false
|
2017-12-03 00:28:18 +01:00
|
|
|
) {
|
2016-11-11 23:13:13 +01:00
|
|
|
return false;
|
|
|
|
}
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2018-01-20 17:48:16 +01:00
|
|
|
/** @var array<string, bool> */
|
|
|
|
$new_stmts_assigned_var_ids = $elseif_context->assigned_var_ids;
|
2018-06-17 02:01:33 +02:00
|
|
|
$elseif_context->assigned_var_ids = $pre_stmts_assigned_var_ids + $new_stmts_assigned_var_ids;
|
|
|
|
|
|
|
|
/** @var array<string, bool> */
|
|
|
|
$new_stmts_possibly_assigned_var_ids = $elseif_context->possibly_assigned_var_ids;
|
|
|
|
$elseif_context->possibly_assigned_var_ids =
|
|
|
|
$pre_stmts_possibly_assigned_var_ids + $new_stmts_possibly_assigned_var_ids;
|
2018-01-20 17:48:16 +01:00
|
|
|
|
2017-02-23 06:25:28 +01:00
|
|
|
if ($elseif_context->byref_constraints !== null) {
|
|
|
|
foreach ($elseif_context->byref_constraints as $var_id => $byref_constraint) {
|
2018-01-25 07:04:26 +01:00
|
|
|
if ($outer_context->byref_constraints !== null
|
|
|
|
&& isset($outer_context->byref_constraints[$var_id])
|
|
|
|
&& ($outer_constraint_type = $outer_context->byref_constraints[$var_id]->type)
|
|
|
|
&& $byref_constraint->type
|
2018-11-06 03:57:36 +01:00
|
|
|
&& !TypeAnalyzer::isContainedBy(
|
|
|
|
$codebase,
|
2017-02-23 06:25:28 +01:00
|
|
|
$byref_constraint->type,
|
2018-01-25 07:04:26 +01:00
|
|
|
$outer_constraint_type
|
2017-02-23 06:25:28 +01:00
|
|
|
)
|
|
|
|
) {
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new ConflictingReferenceConstraint(
|
2018-02-19 19:06:35 +01:00
|
|
|
'There is more than one pass-by-reference constraint on ' . $var_id,
|
2018-11-11 18:01:14 +01:00
|
|
|
new CodeLocation($statements_analyzer, $elseif, $outer_context->include_location, true)
|
2017-02-23 06:25:28 +01:00
|
|
|
),
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer->getSuppressedIssues()
|
2017-02-23 06:25:28 +01:00
|
|
|
)) {
|
|
|
|
// fall through
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$outer_context->byref_constraints[$var_id] = $byref_constraint;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
$final_actions = ScopeAnalyzer::getFinalControlActions(
|
2018-07-13 05:26:08 +02:00
|
|
|
$elseif->stmts,
|
2018-11-06 03:57:36 +01:00
|
|
|
$codebase->config->exit_functions,
|
2018-07-13 05:26:08 +02:00
|
|
|
$outer_context->inside_case
|
|
|
|
);
|
2017-12-03 00:28:18 +01:00
|
|
|
// has a return/throw at end
|
2018-11-06 03:57:36 +01:00
|
|
|
$has_ending_statements = $final_actions === [ScopeAnalyzer::ACTION_END];
|
2017-12-03 00:28:18 +01:00
|
|
|
$has_leaving_statements = $has_ending_statements
|
2018-11-06 03:57:36 +01:00
|
|
|
|| (count($final_actions) && !in_array(ScopeAnalyzer::ACTION_NONE, $final_actions, true));
|
2016-11-11 23:13:13 +01:00
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
$has_break_statement = $final_actions === [ScopeAnalyzer::ACTION_BREAK];
|
|
|
|
$has_continue_statement = $final_actions === [ScopeAnalyzer::ACTION_CONTINUE];
|
|
|
|
$has_leave_switch_statement = $final_actions === [ScopeAnalyzer::ACTION_LEAVE_SWITCH];
|
2017-12-03 00:28:18 +01:00
|
|
|
|
|
|
|
$if_scope->final_actions = array_merge($final_actions, $if_scope->final_actions);
|
|
|
|
|
2018-01-20 17:48:16 +01:00
|
|
|
// update the parent context as necessary
|
|
|
|
$elseif_redefined_vars = $elseif_context->getRedefinedVars($original_context->vars_in_scope);
|
2016-11-11 23:13:13 +01:00
|
|
|
|
2018-01-20 17:48:16 +01:00
|
|
|
if (!$has_leaving_statements) {
|
|
|
|
if ($if_scope->new_vars === null) {
|
|
|
|
$if_scope->new_vars = array_diff_key($elseif_context->vars_in_scope, $outer_context->vars_in_scope);
|
|
|
|
} else {
|
|
|
|
foreach ($if_scope->new_vars as $new_var => $type) {
|
2018-11-11 18:01:14 +01:00
|
|
|
if (!$elseif_context->hasVariable($new_var, $statements_analyzer)) {
|
2018-01-20 17:48:16 +01:00
|
|
|
unset($if_scope->new_vars[$new_var]);
|
|
|
|
} else {
|
|
|
|
$if_scope->new_vars[$new_var] = Type::combineUnionTypes(
|
|
|
|
$type,
|
2019-10-08 19:07:28 +02:00
|
|
|
$elseif_context->vars_in_scope[$new_var],
|
|
|
|
$codebase
|
2018-01-20 17:48:16 +01:00
|
|
|
);
|
2016-10-22 19:23:18 +02:00
|
|
|
}
|
2016-11-11 23:13:13 +01:00
|
|
|
}
|
2018-01-20 17:48:16 +01:00
|
|
|
}
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2018-01-20 17:48:16 +01:00
|
|
|
$possibly_redefined_vars = $elseif_redefined_vars;
|
2016-11-11 23:13:13 +01:00
|
|
|
|
2018-01-20 17:48:16 +01:00
|
|
|
foreach ($possibly_redefined_vars as $var_id => $_) {
|
|
|
|
if (!isset($new_stmts_assigned_var_ids[$var_id])
|
|
|
|
&& in_array($var_id, $changed_var_ids, true)
|
|
|
|
) {
|
|
|
|
unset($possibly_redefined_vars[$var_id]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-27 04:54:54 +02:00
|
|
|
$assigned_var_ids = array_merge($new_stmts_assigned_var_ids, $cond_assigned_var_ids);
|
2018-01-28 23:28:34 +01:00
|
|
|
|
|
|
|
if ($if_scope->assigned_var_ids === null) {
|
|
|
|
$if_scope->assigned_var_ids = $assigned_var_ids;
|
|
|
|
} else {
|
|
|
|
$if_scope->assigned_var_ids = array_intersect_key($assigned_var_ids, $if_scope->assigned_var_ids);
|
|
|
|
}
|
|
|
|
|
2018-01-20 17:48:16 +01:00
|
|
|
if ($if_scope->redefined_vars === null) {
|
|
|
|
$if_scope->redefined_vars = $elseif_redefined_vars;
|
|
|
|
$if_scope->possibly_redefined_vars = $possibly_redefined_vars;
|
|
|
|
} else {
|
|
|
|
foreach ($if_scope->redefined_vars as $redefined_var => $type) {
|
|
|
|
if (!isset($elseif_redefined_vars[$redefined_var])) {
|
|
|
|
unset($if_scope->redefined_vars[$redefined_var]);
|
|
|
|
} else {
|
|
|
|
$if_scope->redefined_vars[$redefined_var] = Type::combineUnionTypes(
|
|
|
|
$elseif_redefined_vars[$redefined_var],
|
2019-10-08 19:07:28 +02:00
|
|
|
$type,
|
|
|
|
$codebase
|
2018-01-20 17:48:16 +01:00
|
|
|
);
|
2018-05-14 22:29:51 +02:00
|
|
|
|
|
|
|
if (isset($outer_context->vars_in_scope[$redefined_var])
|
|
|
|
&& $if_scope->redefined_vars[$redefined_var]->equals(
|
|
|
|
$outer_context->vars_in_scope[$redefined_var]
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
unset($if_scope->redefined_vars[$redefined_var]);
|
|
|
|
}
|
2016-11-11 23:13:13 +01:00
|
|
|
}
|
|
|
|
}
|
2017-03-18 18:37:00 +01:00
|
|
|
|
2018-01-20 17:48:16 +01:00
|
|
|
foreach ($possibly_redefined_vars as $var => $type) {
|
2018-12-08 19:18:55 +01:00
|
|
|
if ($type->hasMixed()) {
|
2018-01-20 17:48:16 +01:00
|
|
|
$if_scope->possibly_redefined_vars[$var] = $type;
|
|
|
|
} elseif (isset($if_scope->possibly_redefined_vars[$var])) {
|
|
|
|
$if_scope->possibly_redefined_vars[$var] = Type::combineUnionTypes(
|
|
|
|
$type,
|
2019-10-08 19:07:28 +02:00
|
|
|
$if_scope->possibly_redefined_vars[$var],
|
|
|
|
$codebase
|
2018-01-20 17:48:16 +01:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
$if_scope->possibly_redefined_vars[$var] = $type;
|
|
|
|
}
|
2017-03-18 19:04:26 +01:00
|
|
|
}
|
2018-01-20 17:48:16 +01:00
|
|
|
}
|
|
|
|
|
2018-05-30 19:57:45 +02:00
|
|
|
$reasonable_clause_count = count($if_scope->reasonable_clauses);
|
|
|
|
|
|
|
|
if ($reasonable_clause_count && $reasonable_clause_count < 20000 && $elseif_clauses) {
|
2018-05-07 07:26:06 +02:00
|
|
|
$if_scope->reasonable_clauses = Algebra::combineOredClauses(
|
2018-01-20 17:48:16 +01:00
|
|
|
$if_scope->reasonable_clauses,
|
|
|
|
$elseif_clauses
|
|
|
|
);
|
2017-03-18 19:04:26 +01:00
|
|
|
} else {
|
|
|
|
$if_scope->reasonable_clauses = [];
|
2016-11-11 23:13:13 +01:00
|
|
|
}
|
2018-01-20 17:48:16 +01:00
|
|
|
} else {
|
|
|
|
$if_scope->reasonable_clauses = [];
|
|
|
|
}
|
2016-11-11 23:13:13 +01:00
|
|
|
|
2018-01-20 17:48:16 +01:00
|
|
|
if ($negated_elseif_types) {
|
|
|
|
if ($has_leaving_statements) {
|
|
|
|
$changed_var_ids = [];
|
2017-03-14 20:48:52 +01:00
|
|
|
|
2018-01-20 17:48:16 +01:00
|
|
|
$leaving_vars_reconciled = Reconciler::reconcileKeyedTypes(
|
|
|
|
$negated_elseif_types,
|
|
|
|
$pre_conditional_context->vars_in_scope,
|
|
|
|
$changed_var_ids,
|
|
|
|
[],
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer,
|
2019-07-25 14:33:27 +02:00
|
|
|
$statements_analyzer->getTemplateTypeMap() ?: [],
|
2018-12-08 19:18:55 +01:00
|
|
|
$elseif_context->inside_loop,
|
2018-11-14 19:44:20 +01:00
|
|
|
new CodeLocation($statements_analyzer->getSource(), $elseif, $outer_context->include_location)
|
2018-01-20 17:48:16 +01:00
|
|
|
);
|
2017-03-14 20:48:52 +01:00
|
|
|
|
2018-01-20 17:48:16 +01:00
|
|
|
$implied_outer_context = clone $elseif_context;
|
|
|
|
$implied_outer_context->vars_in_scope = $leaving_vars_reconciled;
|
2017-03-14 20:48:52 +01:00
|
|
|
|
2018-01-20 17:48:16 +01:00
|
|
|
$outer_context->update(
|
|
|
|
$elseif_context,
|
|
|
|
$implied_outer_context,
|
|
|
|
false,
|
|
|
|
array_keys($negated_elseif_types),
|
|
|
|
$if_scope->updated_vars
|
|
|
|
);
|
2016-11-11 23:13:13 +01:00
|
|
|
}
|
2018-01-20 17:48:16 +01:00
|
|
|
}
|
2016-11-11 23:13:13 +01:00
|
|
|
|
2018-01-20 17:48:16 +01:00
|
|
|
if (!$has_ending_statements) {
|
2018-05-18 17:02:50 +02:00
|
|
|
$vars_possibly_in_scope = array_diff_key(
|
|
|
|
$elseif_context->vars_possibly_in_scope,
|
|
|
|
$outer_context->vars_possibly_in_scope
|
|
|
|
);
|
|
|
|
|
2018-06-17 02:01:33 +02:00
|
|
|
$possibly_assigned_var_ids = $new_stmts_possibly_assigned_var_ids;
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2018-06-17 03:54:44 +02:00
|
|
|
if ($has_leaving_statements && $elseif_context->loop_scope) {
|
2018-01-20 17:48:16 +01:00
|
|
|
if (!$has_continue_statement && !$has_break_statement) {
|
|
|
|
$if_scope->new_vars_possibly_in_scope = array_merge(
|
2018-05-18 17:02:50 +02:00
|
|
|
$vars_possibly_in_scope,
|
2018-01-20 17:48:16 +01:00
|
|
|
$if_scope->new_vars_possibly_in_scope
|
2016-11-02 07:29:00 +01:00
|
|
|
);
|
2018-05-18 17:02:50 +02:00
|
|
|
$if_scope->possibly_assigned_var_ids = array_merge(
|
|
|
|
$possibly_assigned_var_ids,
|
|
|
|
$if_scope->possibly_assigned_var_ids
|
|
|
|
);
|
2016-10-22 19:23:18 +02:00
|
|
|
}
|
2018-01-20 17:48:16 +01:00
|
|
|
|
2018-06-17 03:54:44 +02:00
|
|
|
$elseif_context->loop_scope->vars_possibly_in_scope = array_merge(
|
2018-05-18 17:02:50 +02:00
|
|
|
$vars_possibly_in_scope,
|
2018-06-17 03:54:44 +02:00
|
|
|
$elseif_context->loop_scope->vars_possibly_in_scope
|
2018-01-20 17:48:16 +01:00
|
|
|
);
|
|
|
|
} elseif (!$has_leaving_statements) {
|
2018-05-18 17:02:50 +02:00
|
|
|
$if_scope->new_vars_possibly_in_scope = array_merge(
|
|
|
|
$vars_possibly_in_scope,
|
|
|
|
$if_scope->new_vars_possibly_in_scope
|
|
|
|
);
|
|
|
|
$if_scope->possibly_assigned_var_ids = array_merge(
|
|
|
|
$possibly_assigned_var_ids,
|
|
|
|
$if_scope->possibly_assigned_var_ids
|
|
|
|
);
|
2016-11-11 23:13:13 +01:00
|
|
|
}
|
2018-06-17 02:01:33 +02:00
|
|
|
|
|
|
|
if ($outer_context->collect_references && (!$has_leaving_statements || $has_leave_switch_statement)) {
|
|
|
|
foreach ($elseif_context->unreferenced_vars as $var_id => $locations) {
|
|
|
|
if (!isset($outer_context->unreferenced_vars[$var_id])) {
|
|
|
|
if (isset($if_scope->new_unreferenced_vars[$var_id])) {
|
|
|
|
$if_scope->new_unreferenced_vars[$var_id] += $locations;
|
|
|
|
} else {
|
|
|
|
$if_scope->new_unreferenced_vars[$var_id] = $locations;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$new_locations = array_diff_key(
|
|
|
|
$locations,
|
|
|
|
$outer_context->unreferenced_vars[$var_id]
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($new_locations) {
|
|
|
|
if (isset($if_scope->new_unreferenced_vars[$var_id])) {
|
|
|
|
$if_scope->new_unreferenced_vars[$var_id] += $locations;
|
|
|
|
} else {
|
|
|
|
$if_scope->new_unreferenced_vars[$var_id] = $locations;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-11-11 23:13:13 +01:00
|
|
|
}
|
2016-12-27 19:58:58 +01:00
|
|
|
|
2017-02-27 05:09:18 +01:00
|
|
|
if ($outer_context->collect_references) {
|
2017-11-24 18:17:28 +01:00
|
|
|
$outer_context->referenced_var_ids = array_merge(
|
|
|
|
$outer_context->referenced_var_ids,
|
|
|
|
$elseif_context->referenced_var_ids
|
2017-02-01 05:24:33 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-06-22 07:13:49 +02:00
|
|
|
if ($outer_context->collect_exceptions) {
|
2019-03-29 00:43:14 +01:00
|
|
|
$outer_context->mergeExceptions($elseif_context);
|
2018-06-22 07:13:49 +02:00
|
|
|
}
|
|
|
|
|
2019-08-07 04:58:53 +02:00
|
|
|
try {
|
|
|
|
$if_scope->negated_clauses = array_merge(
|
|
|
|
$if_scope->negated_clauses,
|
|
|
|
Algebra::negateFormula($elseif_clauses)
|
|
|
|
);
|
|
|
|
} catch (\Psalm\Exception\ComplicatedExpressionException $e) {
|
|
|
|
$if_scope->negated_clauses = [];
|
|
|
|
}
|
2016-11-11 23:13:13 +01:00
|
|
|
}
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2016-11-11 23:13:13 +01:00
|
|
|
/**
|
2018-11-11 18:01:14 +01:00
|
|
|
* @param StatementsAnalyzer $statements_analyzer
|
2018-05-31 00:56:44 +02:00
|
|
|
* @param PhpParser\Node\Stmt\Else_|null $else
|
2016-11-11 23:13:13 +01:00
|
|
|
* @param IfScope $if_scope
|
|
|
|
* @param Context $else_context
|
|
|
|
* @param Context $outer_context
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-11-11 23:13:13 +01:00
|
|
|
* @return false|null
|
|
|
|
*/
|
2017-01-07 21:09:47 +01:00
|
|
|
protected static function analyzeElseBlock(
|
2018-11-11 18:01:14 +01:00
|
|
|
StatementsAnalyzer $statements_analyzer,
|
2018-05-31 00:56:44 +02:00
|
|
|
$else,
|
2016-11-11 23:13:13 +01:00
|
|
|
IfScope $if_scope,
|
|
|
|
Context $else_context,
|
2018-06-17 03:54:44 +02:00
|
|
|
Context $outer_context
|
2016-11-11 23:13:13 +01:00
|
|
|
) {
|
2018-11-11 18:01:14 +01:00
|
|
|
$codebase = $statements_analyzer->getCodebase();
|
2017-09-03 00:15:52 +02:00
|
|
|
|
2018-06-01 04:20:38 +02:00
|
|
|
if (!$else && !$if_scope->negated_clauses && !$else_context->clauses) {
|
2018-11-06 03:57:36 +01:00
|
|
|
$if_scope->final_actions = array_merge([ScopeAnalyzer::ACTION_NONE], $if_scope->final_actions);
|
2018-06-01 04:20:38 +02:00
|
|
|
$if_scope->assigned_var_ids = [];
|
|
|
|
$if_scope->new_vars = [];
|
|
|
|
$if_scope->redefined_vars = [];
|
|
|
|
$if_scope->reasonable_clauses = [];
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2018-05-07 07:26:06 +02:00
|
|
|
$else_context->clauses = Algebra::simplifyCNF(
|
2016-12-27 19:58:58 +01:00
|
|
|
array_merge(
|
2018-05-06 02:52:10 +02:00
|
|
|
$else_context->clauses,
|
2017-03-16 19:45:45 +01:00
|
|
|
$if_scope->negated_clauses
|
2016-12-27 19:58:58 +01:00
|
|
|
)
|
|
|
|
);
|
|
|
|
|
2018-05-07 07:26:06 +02:00
|
|
|
$else_types = Algebra::getTruthsFromFormula($else_context->clauses);
|
2016-12-27 19:58:58 +01:00
|
|
|
|
2018-06-01 04:20:38 +02:00
|
|
|
if (!$else && !$else_types) {
|
2018-11-06 03:57:36 +01:00
|
|
|
$if_scope->final_actions = array_merge([ScopeAnalyzer::ACTION_NONE], $if_scope->final_actions);
|
2018-06-01 04:20:38 +02:00
|
|
|
$if_scope->assigned_var_ids = [];
|
|
|
|
$if_scope->new_vars = [];
|
|
|
|
$if_scope->redefined_vars = [];
|
|
|
|
$if_scope->reasonable_clauses = [];
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$original_context = clone $else_context;
|
|
|
|
|
2016-12-27 19:58:58 +01:00
|
|
|
if ($else_types) {
|
2017-11-28 06:46:41 +01:00
|
|
|
$changed_var_ids = [];
|
2016-12-28 20:20:16 +01:00
|
|
|
|
2017-12-29 16:55:41 +01:00
|
|
|
$else_vars_reconciled = Reconciler::reconcileKeyedTypes(
|
2016-12-27 19:58:58 +01:00
|
|
|
$else_types,
|
2016-11-11 23:13:13 +01:00
|
|
|
$else_context->vars_in_scope,
|
2017-11-28 06:46:41 +01:00
|
|
|
$changed_var_ids,
|
|
|
|
[],
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer,
|
2019-01-23 05:42:54 +01:00
|
|
|
[],
|
2018-12-08 19:18:55 +01:00
|
|
|
$else_context->inside_loop,
|
2018-05-31 00:56:44 +02:00
|
|
|
$else
|
2018-11-11 18:01:14 +01:00
|
|
|
? new CodeLocation($statements_analyzer->getSource(), $else, $outer_context->include_location)
|
2018-11-14 19:44:20 +01:00
|
|
|
: null
|
2016-11-11 23:13:13 +01:00
|
|
|
);
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2016-11-11 23:13:13 +01:00
|
|
|
$else_context->vars_in_scope = $else_vars_reconciled;
|
2017-11-28 06:46:41 +01:00
|
|
|
|
|
|
|
$else_context->removeReconciledClauses($changed_var_ids);
|
2016-11-11 23:13:13 +01:00
|
|
|
}
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2016-11-11 23:13:13 +01:00
|
|
|
$old_else_context = clone $else_context;
|
2016-11-02 07:29:00 +01:00
|
|
|
|
2018-01-28 23:28:34 +01:00
|
|
|
$pre_stmts_assigned_var_ids = $else_context->assigned_var_ids;
|
|
|
|
$else_context->assigned_var_ids = [];
|
|
|
|
|
2018-06-17 02:01:33 +02:00
|
|
|
$pre_possibly_assigned_var_ids = $else_context->possibly_assigned_var_ids;
|
|
|
|
$else_context->possibly_assigned_var_ids = [];
|
|
|
|
|
2018-05-31 00:56:44 +02:00
|
|
|
if ($else) {
|
2018-11-11 18:01:14 +01:00
|
|
|
if ($statements_analyzer->analyze(
|
2018-05-31 00:56:44 +02:00
|
|
|
$else->stmts,
|
2018-06-17 03:54:44 +02:00
|
|
|
$else_context
|
2018-05-31 00:56:44 +02:00
|
|
|
) === false
|
|
|
|
) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-11-11 23:13:13 +01:00
|
|
|
}
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2018-01-28 23:28:34 +01:00
|
|
|
/** @var array<string, bool> */
|
|
|
|
$new_assigned_var_ids = $else_context->assigned_var_ids;
|
|
|
|
$else_context->assigned_var_ids = $pre_stmts_assigned_var_ids;
|
|
|
|
|
2018-06-17 02:01:33 +02:00
|
|
|
/** @var array<string, bool> */
|
|
|
|
$new_possibly_assigned_var_ids = $else_context->possibly_assigned_var_ids;
|
|
|
|
$else_context->possibly_assigned_var_ids = $pre_possibly_assigned_var_ids + $new_possibly_assigned_var_ids;
|
|
|
|
|
2018-05-31 00:56:44 +02:00
|
|
|
if ($else && $else_context->byref_constraints !== null) {
|
2017-02-23 06:25:28 +01:00
|
|
|
foreach ($else_context->byref_constraints as $var_id => $byref_constraint) {
|
2018-01-25 07:04:26 +01:00
|
|
|
if ($outer_context->byref_constraints !== null
|
|
|
|
&& isset($outer_context->byref_constraints[$var_id])
|
|
|
|
&& ($outer_constraint_type = $outer_context->byref_constraints[$var_id]->type)
|
|
|
|
&& $byref_constraint->type
|
2018-11-06 03:57:36 +01:00
|
|
|
&& !TypeAnalyzer::isContainedBy(
|
|
|
|
$codebase,
|
2017-02-23 06:25:28 +01:00
|
|
|
$byref_constraint->type,
|
2018-01-25 07:04:26 +01:00
|
|
|
$outer_constraint_type
|
2017-02-23 06:25:28 +01:00
|
|
|
)
|
|
|
|
) {
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new ConflictingReferenceConstraint(
|
2018-02-19 19:06:35 +01:00
|
|
|
'There is more than one pass-by-reference constraint on ' . $var_id,
|
2018-11-11 18:01:14 +01:00
|
|
|
new CodeLocation($statements_analyzer, $else, $outer_context->include_location, true)
|
2017-02-23 06:25:28 +01:00
|
|
|
),
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer->getSuppressedIssues()
|
2017-02-23 06:25:28 +01:00
|
|
|
)) {
|
|
|
|
// fall through
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$outer_context->byref_constraints[$var_id] = $byref_constraint;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-31 00:56:44 +02:00
|
|
|
if ($else && $outer_context->collect_references) {
|
2017-11-24 18:17:28 +01:00
|
|
|
$outer_context->referenced_var_ids = array_merge(
|
|
|
|
$outer_context->referenced_var_ids,
|
|
|
|
$else_context->referenced_var_ids
|
2017-02-02 06:45:23 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-06-17 02:01:33 +02:00
|
|
|
$final_actions = $else
|
2018-11-06 03:57:36 +01:00
|
|
|
? ScopeAnalyzer::getFinalControlActions(
|
2018-07-13 05:26:08 +02:00
|
|
|
$else->stmts,
|
2018-11-06 03:57:36 +01:00
|
|
|
$codebase->config->exit_functions,
|
2018-07-13 05:26:08 +02:00
|
|
|
$outer_context->inside_case
|
|
|
|
)
|
2018-11-06 03:57:36 +01:00
|
|
|
: [ScopeAnalyzer::ACTION_NONE];
|
2017-12-03 00:28:18 +01:00
|
|
|
// has a return/throw at end
|
2018-11-06 03:57:36 +01:00
|
|
|
$has_ending_statements = $final_actions === [ScopeAnalyzer::ACTION_END];
|
2017-12-03 00:28:18 +01:00
|
|
|
$has_leaving_statements = $has_ending_statements
|
2018-11-06 03:57:36 +01:00
|
|
|
|| (count($final_actions) && !in_array(ScopeAnalyzer::ACTION_NONE, $final_actions, true));
|
2017-12-03 00:28:18 +01:00
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
$has_break_statement = $final_actions === [ScopeAnalyzer::ACTION_BREAK];
|
|
|
|
$has_continue_statement = $final_actions === [ScopeAnalyzer::ACTION_CONTINUE];
|
|
|
|
$has_leave_switch_statement = $final_actions === [ScopeAnalyzer::ACTION_LEAVE_SWITCH];
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2017-12-03 00:28:18 +01:00
|
|
|
$if_scope->final_actions = array_merge($final_actions, $if_scope->final_actions);
|
|
|
|
|
2018-01-20 17:48:16 +01:00
|
|
|
$else_redefined_vars = $else_context->getRedefinedVars($original_context->vars_in_scope);
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2018-01-20 17:48:16 +01:00
|
|
|
// if it doesn't end in a return
|
|
|
|
if (!$has_leaving_statements) {
|
2018-06-01 04:20:38 +02:00
|
|
|
if ($if_scope->new_vars === null && $else) {
|
2018-01-20 17:48:16 +01:00
|
|
|
$if_scope->new_vars = array_diff_key($else_context->vars_in_scope, $outer_context->vars_in_scope);
|
2018-06-01 04:20:38 +02:00
|
|
|
} elseif ($if_scope->new_vars !== null) {
|
2018-01-20 17:48:16 +01:00
|
|
|
foreach ($if_scope->new_vars as $new_var => $type) {
|
|
|
|
if (!$else_context->hasVariable($new_var)) {
|
|
|
|
unset($if_scope->new_vars[$new_var]);
|
|
|
|
} else {
|
|
|
|
$if_scope->new_vars[$new_var] = Type::combineUnionTypes(
|
|
|
|
$type,
|
2019-10-08 19:07:28 +02:00
|
|
|
$else_context->vars_in_scope[$new_var],
|
|
|
|
$codebase
|
2018-01-20 17:48:16 +01:00
|
|
|
);
|
2016-11-11 23:13:13 +01:00
|
|
|
}
|
|
|
|
}
|
2018-01-20 17:48:16 +01:00
|
|
|
}
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2018-01-28 23:28:34 +01:00
|
|
|
if ($if_scope->assigned_var_ids === null) {
|
|
|
|
$if_scope->assigned_var_ids = $new_assigned_var_ids;
|
|
|
|
} else {
|
|
|
|
$if_scope->assigned_var_ids = array_intersect_key($new_assigned_var_ids, $if_scope->assigned_var_ids);
|
|
|
|
}
|
|
|
|
|
2018-01-20 17:48:16 +01:00
|
|
|
if ($if_scope->redefined_vars === null) {
|
|
|
|
$if_scope->redefined_vars = $else_redefined_vars;
|
|
|
|
$if_scope->possibly_redefined_vars = $if_scope->redefined_vars;
|
|
|
|
} else {
|
|
|
|
foreach ($if_scope->redefined_vars as $redefined_var => $type) {
|
|
|
|
if (!isset($else_redefined_vars[$redefined_var])) {
|
|
|
|
unset($if_scope->redefined_vars[$redefined_var]);
|
|
|
|
} else {
|
|
|
|
$if_scope->redefined_vars[$redefined_var] = Type::combineUnionTypes(
|
|
|
|
$else_redefined_vars[$redefined_var],
|
2019-10-08 19:07:28 +02:00
|
|
|
$type,
|
|
|
|
$codebase
|
2018-01-20 17:48:16 +01:00
|
|
|
);
|
2016-11-11 23:13:13 +01:00
|
|
|
}
|
2018-01-20 17:48:16 +01:00
|
|
|
}
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2018-01-20 17:48:16 +01:00
|
|
|
foreach ($else_redefined_vars as $var => $type) {
|
2018-12-08 19:18:55 +01:00
|
|
|
if ($type->hasMixed()) {
|
2018-01-20 17:48:16 +01:00
|
|
|
$if_scope->possibly_redefined_vars[$var] = $type;
|
|
|
|
} elseif (isset($if_scope->possibly_redefined_vars[$var])) {
|
|
|
|
$if_scope->possibly_redefined_vars[$var] = Type::combineUnionTypes(
|
|
|
|
$type,
|
2019-10-08 19:07:28 +02:00
|
|
|
$if_scope->possibly_redefined_vars[$var],
|
|
|
|
$codebase
|
2018-01-20 17:48:16 +01:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
$if_scope->possibly_redefined_vars[$var] = $type;
|
2016-10-22 19:23:18 +02:00
|
|
|
}
|
2016-11-11 23:13:13 +01:00
|
|
|
}
|
|
|
|
}
|
2018-05-14 22:29:51 +02:00
|
|
|
|
|
|
|
$if_scope->reasonable_clauses = [];
|
2018-01-20 17:48:16 +01:00
|
|
|
}
|
2016-11-11 23:13:13 +01:00
|
|
|
|
2018-01-20 17:48:16 +01:00
|
|
|
// update the parent context as necessary
|
|
|
|
if ($if_scope->negatable_if_types) {
|
|
|
|
$outer_context->update(
|
|
|
|
$old_else_context,
|
|
|
|
$else_context,
|
|
|
|
$has_leaving_statements,
|
|
|
|
array_keys($if_scope->negatable_if_types),
|
|
|
|
$if_scope->updated_vars
|
|
|
|
);
|
|
|
|
}
|
2016-11-11 23:13:13 +01:00
|
|
|
|
2018-01-20 17:48:16 +01:00
|
|
|
if (!$has_ending_statements) {
|
2018-05-18 17:02:50 +02:00
|
|
|
$vars_possibly_in_scope = array_diff_key(
|
|
|
|
$else_context->vars_possibly_in_scope,
|
|
|
|
$outer_context->vars_possibly_in_scope
|
|
|
|
);
|
|
|
|
|
2018-06-17 02:01:33 +02:00
|
|
|
$possibly_assigned_var_ids = $new_possibly_assigned_var_ids;
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2018-06-17 03:54:44 +02:00
|
|
|
if ($has_leaving_statements && $else_context->loop_scope) {
|
2018-01-20 17:48:16 +01:00
|
|
|
if (!$has_continue_statement && !$has_break_statement) {
|
|
|
|
$if_scope->new_vars_possibly_in_scope = array_merge(
|
2018-05-18 17:02:50 +02:00
|
|
|
$vars_possibly_in_scope,
|
2018-01-20 17:48:16 +01:00
|
|
|
$if_scope->new_vars_possibly_in_scope
|
2016-11-02 07:29:00 +01:00
|
|
|
);
|
2018-05-18 17:02:50 +02:00
|
|
|
|
|
|
|
$if_scope->possibly_assigned_var_ids = array_merge(
|
|
|
|
$possibly_assigned_var_ids,
|
|
|
|
$if_scope->possibly_assigned_var_ids
|
|
|
|
);
|
2016-10-22 19:23:18 +02:00
|
|
|
}
|
2018-01-20 17:48:16 +01:00
|
|
|
|
2018-06-17 03:54:44 +02:00
|
|
|
$else_context->loop_scope->vars_possibly_in_scope = array_merge(
|
2018-05-18 17:02:50 +02:00
|
|
|
$vars_possibly_in_scope,
|
2018-06-17 03:54:44 +02:00
|
|
|
$else_context->loop_scope->vars_possibly_in_scope
|
2018-01-20 17:48:16 +01:00
|
|
|
);
|
|
|
|
} elseif (!$has_leaving_statements) {
|
2018-05-18 17:02:50 +02:00
|
|
|
$if_scope->new_vars_possibly_in_scope = array_merge(
|
|
|
|
$vars_possibly_in_scope,
|
|
|
|
$if_scope->new_vars_possibly_in_scope
|
|
|
|
);
|
|
|
|
|
|
|
|
$if_scope->possibly_assigned_var_ids = array_merge(
|
|
|
|
$possibly_assigned_var_ids,
|
|
|
|
$if_scope->possibly_assigned_var_ids
|
|
|
|
);
|
2016-10-22 19:23:18 +02:00
|
|
|
}
|
2018-06-17 02:01:33 +02:00
|
|
|
|
|
|
|
if ($outer_context->collect_references && (!$has_leaving_statements || $has_leave_switch_statement)) {
|
|
|
|
foreach ($else_context->unreferenced_vars as $var_id => $locations) {
|
|
|
|
if (!isset($outer_context->unreferenced_vars[$var_id])) {
|
|
|
|
if (isset($if_scope->new_unreferenced_vars[$var_id])) {
|
|
|
|
$if_scope->new_unreferenced_vars[$var_id] += $locations;
|
|
|
|
} else {
|
|
|
|
$if_scope->new_unreferenced_vars[$var_id] = $locations;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$new_locations = array_diff_key(
|
|
|
|
$locations,
|
|
|
|
$outer_context->unreferenced_vars[$var_id]
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($new_locations) {
|
|
|
|
if (isset($if_scope->new_unreferenced_vars[$var_id])) {
|
|
|
|
$if_scope->new_unreferenced_vars[$var_id] += $locations;
|
|
|
|
} else {
|
|
|
|
$if_scope->new_unreferenced_vars[$var_id] = $locations;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-10-22 19:23:18 +02:00
|
|
|
}
|
2017-09-03 00:15:52 +02:00
|
|
|
|
2018-06-22 07:13:49 +02:00
|
|
|
if ($outer_context->collect_exceptions) {
|
2019-03-29 00:43:14 +01:00
|
|
|
$outer_context->mergeExceptions($else_context);
|
2018-06-22 07:13:49 +02:00
|
|
|
}
|
2016-10-22 19:23:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-05-14 18:25:58 +02:00
|
|
|
* Returns statements that are definitely evaluated before any statements after the end of the
|
|
|
|
* if/elseif/else blocks
|
|
|
|
*
|
2016-10-22 19:23:18 +02:00
|
|
|
* @param PhpParser\Node\Expr $stmt
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-10-22 19:23:18 +02:00
|
|
|
* @return PhpParser\Node\Expr|null
|
|
|
|
*/
|
2019-05-02 19:43:18 +02:00
|
|
|
public static function getDefinitelyEvaluatedExpression(PhpParser\Node\Expr $stmt)
|
2016-10-22 19:23:18 +02:00
|
|
|
{
|
|
|
|
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp) {
|
2018-05-13 00:46:47 +02:00
|
|
|
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\BooleanAnd
|
|
|
|
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\LogicalAnd
|
|
|
|
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\LogicalXor
|
2016-12-28 23:04:03 +01:00
|
|
|
) {
|
|
|
|
return self::getDefinitelyEvaluatedExpression($stmt->left);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $stmt;
|
|
|
|
}
|
|
|
|
|
2017-01-07 20:35:07 +01:00
|
|
|
return $stmt;
|
2016-10-22 19:23:18 +02:00
|
|
|
}
|
|
|
|
}
|