2018-01-14 18:09:40 +01:00
|
|
|
<?php
|
|
|
|
namespace Psalm\Checker\Statements\Expression\Fetch;
|
|
|
|
|
|
|
|
use PhpParser;
|
2018-05-01 04:13:13 +02:00
|
|
|
use Psalm\Checker\FunctionLikeChecker;
|
2018-01-14 18:09:40 +01:00
|
|
|
use Psalm\Checker\Statements\ExpressionChecker;
|
|
|
|
use Psalm\Checker\StatementsChecker;
|
|
|
|
use Psalm\CodeLocation;
|
|
|
|
use Psalm\Context;
|
|
|
|
use Psalm\Issue\InvalidScope;
|
|
|
|
use Psalm\Issue\PossiblyUndefinedGlobalVariable;
|
|
|
|
use Psalm\Issue\PossiblyUndefinedVariable;
|
|
|
|
use Psalm\Issue\UndefinedGlobalVariable;
|
|
|
|
use Psalm\Issue\UndefinedVariable;
|
|
|
|
use Psalm\IssueBuffer;
|
|
|
|
use Psalm\Type;
|
|
|
|
|
|
|
|
class VariableFetchChecker
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @param StatementsChecker $statements_checker
|
|
|
|
* @param PhpParser\Node\Expr\Variable $stmt
|
|
|
|
* @param Context $context
|
|
|
|
* @param bool $passed_by_reference
|
|
|
|
* @param Type\Union|null $by_ref_type
|
|
|
|
* @param bool $array_assignment
|
|
|
|
*
|
|
|
|
* @return false|null
|
|
|
|
*/
|
|
|
|
public static function analyze(
|
|
|
|
StatementsChecker $statements_checker,
|
|
|
|
PhpParser\Node\Expr\Variable $stmt,
|
|
|
|
Context $context,
|
|
|
|
$passed_by_reference = false,
|
|
|
|
Type\Union $by_ref_type = null,
|
|
|
|
$array_assignment = false
|
|
|
|
) {
|
|
|
|
if ($stmt->name === 'this') {
|
|
|
|
if ($statements_checker->isStatic()) {
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new InvalidScope(
|
|
|
|
'Invalid reference to $this in a static context',
|
|
|
|
new CodeLocation($statements_checker->getSource(), $stmt)
|
|
|
|
),
|
|
|
|
$statements_checker->getSuppressedIssues()
|
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
} elseif (!isset($context->vars_in_scope['$this'])) {
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new InvalidScope(
|
|
|
|
'Invalid reference to $this in a non-class context',
|
|
|
|
new CodeLocation($statements_checker->getSource(), $stmt)
|
|
|
|
),
|
|
|
|
$statements_checker->getSuppressedIssues()
|
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$stmt->inferredType = clone $context->vars_in_scope['$this'];
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$context->check_variables) {
|
|
|
|
if (is_string($stmt->name)) {
|
|
|
|
$var_name = '$' . $stmt->name;
|
|
|
|
|
2018-01-28 23:28:34 +01:00
|
|
|
if (!$context->hasVariable($var_name, $statements_checker)) {
|
2018-01-14 18:09:40 +01:00
|
|
|
$context->vars_in_scope[$var_name] = Type::getMixed();
|
|
|
|
$context->vars_possibly_in_scope[$var_name] = true;
|
|
|
|
$stmt->inferredType = Type::getMixed();
|
|
|
|
} else {
|
|
|
|
$stmt->inferredType = clone $context->vars_in_scope[$var_name];
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$stmt->inferredType = Type::getMixed();
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (in_array(
|
|
|
|
$stmt->name,
|
|
|
|
[
|
|
|
|
'GLOBALS',
|
|
|
|
'_SERVER',
|
|
|
|
'_GET',
|
|
|
|
'_POST',
|
|
|
|
'_FILES',
|
|
|
|
'_COOKIE',
|
|
|
|
'_SESSION',
|
|
|
|
'_REQUEST',
|
|
|
|
'_ENV',
|
|
|
|
],
|
|
|
|
true
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
$stmt->inferredType = Type::getArray();
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2018-03-18 15:38:08 +01:00
|
|
|
if ($context->is_global && ($stmt->name === 'argv' || $stmt->name === 'argc')) {
|
|
|
|
$var_name = '$' . $stmt->name;
|
|
|
|
|
|
|
|
if (!$context->hasVariable($var_name, $statements_checker)) {
|
|
|
|
if ($stmt->name === 'argv') {
|
|
|
|
$context->vars_in_scope[$var_name] = new Type\Union([
|
|
|
|
new Type\Atomic\TArray([
|
|
|
|
Type::getInt(),
|
|
|
|
Type::getString(),
|
|
|
|
]),
|
|
|
|
]);
|
|
|
|
} else {
|
|
|
|
$context->vars_in_scope[$var_name] = Type::getInt();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$context->vars_possibly_in_scope[$var_name] = true;
|
|
|
|
$stmt->inferredType = clone $context->vars_in_scope[$var_name];
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2018-01-14 18:09:40 +01:00
|
|
|
if (!is_string($stmt->name)) {
|
|
|
|
return ExpressionChecker::analyze($statements_checker, $stmt->name, $context);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($passed_by_reference && $by_ref_type) {
|
|
|
|
ExpressionChecker::assignByRefParam($statements_checker, $stmt, $by_ref_type, $context);
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$var_name = '$' . $stmt->name;
|
|
|
|
|
2018-01-28 23:28:34 +01:00
|
|
|
if (!$context->hasVariable($var_name, $statements_checker)) {
|
2018-01-14 18:09:40 +01:00
|
|
|
if (!isset($context->vars_possibly_in_scope[$var_name]) ||
|
|
|
|
!$statements_checker->getFirstAppearance($var_name)
|
|
|
|
) {
|
|
|
|
if ($array_assignment) {
|
|
|
|
// if we're in an array assignment, let's assign the variable
|
|
|
|
// because PHP allows it
|
|
|
|
|
|
|
|
$context->vars_in_scope[$var_name] = Type::getArray();
|
|
|
|
$context->vars_possibly_in_scope[$var_name] = true;
|
|
|
|
|
|
|
|
// it might have been defined first in another if/else branch
|
|
|
|
if (!$statements_checker->hasVariable($var_name)) {
|
|
|
|
$statements_checker->registerVariable(
|
|
|
|
$var_name,
|
2018-01-21 22:24:20 +01:00
|
|
|
new CodeLocation($statements_checker, $stmt),
|
|
|
|
$context->branch_point
|
2018-01-14 18:09:40 +01:00
|
|
|
);
|
|
|
|
}
|
2018-05-01 04:13:13 +02:00
|
|
|
} elseif (!$context->inside_isset
|
|
|
|
|| $statements_checker->getSource() instanceof FunctionLikeChecker
|
|
|
|
) {
|
2018-01-14 18:09:40 +01:00
|
|
|
if ($context->is_global) {
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new UndefinedGlobalVariable(
|
|
|
|
'Cannot find referenced variable ' . $var_name . ' in global scope',
|
|
|
|
new CodeLocation($statements_checker->getSource(), $stmt)
|
|
|
|
),
|
|
|
|
$statements_checker->getSuppressedIssues()
|
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$stmt->inferredType = Type::getMixed();
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
IssueBuffer::add(
|
|
|
|
new UndefinedVariable(
|
|
|
|
'Cannot find referenced variable ' . $var_name,
|
|
|
|
new CodeLocation($statements_checker->getSource(), $stmt)
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
$stmt->inferredType = Type::getMixed();
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$first_appearance = $statements_checker->getFirstAppearance($var_name);
|
|
|
|
|
2018-02-10 16:30:08 +01:00
|
|
|
if ($first_appearance && !$context->inside_isset && !$context->inside_unset) {
|
2018-01-21 22:24:20 +01:00
|
|
|
$project_checker = $statements_checker->getFileChecker()->project_checker;
|
|
|
|
|
2018-01-22 19:54:32 +01:00
|
|
|
if ($context->is_global) {
|
|
|
|
if ($project_checker->alter_code) {
|
|
|
|
if (!isset($project_checker->getIssuesToFix()['PossiblyUndefinedGlobalVariable'])) {
|
|
|
|
return;
|
|
|
|
}
|
2018-01-21 22:24:20 +01:00
|
|
|
|
2018-01-22 19:54:32 +01:00
|
|
|
$branch_point = $statements_checker->getBranchPoint($var_name);
|
2018-01-21 22:24:20 +01:00
|
|
|
|
2018-01-22 19:54:32 +01:00
|
|
|
if ($branch_point) {
|
|
|
|
$statements_checker->addVariableInitialization($var_name, $branch_point);
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
2018-01-21 22:24:20 +01:00
|
|
|
|
2018-01-14 18:09:40 +01:00
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new PossiblyUndefinedGlobalVariable(
|
|
|
|
'Possibly undefined global variable ' . $var_name . ', first seen on line ' .
|
|
|
|
$first_appearance->getLineNumber(),
|
|
|
|
new CodeLocation($statements_checker->getSource(), $stmt)
|
|
|
|
),
|
|
|
|
$statements_checker->getSuppressedIssues()
|
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
2018-01-22 19:54:32 +01:00
|
|
|
if ($project_checker->alter_code) {
|
|
|
|
if (!isset($project_checker->getIssuesToFix()['PossiblyUndefinedVariable'])) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$branch_point = $statements_checker->getBranchPoint($var_name);
|
|
|
|
|
|
|
|
if ($branch_point) {
|
|
|
|
$statements_checker->addVariableInitialization($var_name, $branch_point);
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-01-14 18:09:40 +01:00
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new PossiblyUndefinedVariable(
|
|
|
|
'Possibly undefined variable ' . $var_name . ', first seen on line ' .
|
|
|
|
$first_appearance->getLineNumber(),
|
|
|
|
new CodeLocation($statements_checker->getSource(), $stmt)
|
|
|
|
),
|
|
|
|
$statements_checker->getSuppressedIssues()
|
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2018-01-29 18:13:44 +01:00
|
|
|
|
|
|
|
$statements_checker->registerVariableUse($first_appearance);
|
2018-01-14 18:09:40 +01:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$stmt->inferredType = clone $context->vars_in_scope[$var_name];
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|