Skip to content

Commit

Permalink
Avoid polluting the scope by adding superglobal expressions lazily
Browse files Browse the repository at this point in the history
  • Loading branch information
herndlm committed Jan 7, 2025
1 parent 95b8360 commit 86901a7
Show file tree
Hide file tree
Showing 5 changed files with 31 additions and 90 deletions.
20 changes: 18 additions & 2 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,10 @@ public function afterOpenSslCall(string $openSslFunctionName): self
/** @api */
public function hasVariableType(string $variableName): TrinaryLogic
{
if ($this->isGlobalVariable($variableName)) {
return TrinaryLogic::createYes();
}

$varExprString = '$' . $variableName;
if (!isset($this->expressionTypes[$varExprString])) {
if ($this->canAnyVariableExist()) {
Expand Down Expand Up @@ -537,13 +541,20 @@ public function getVariableType(string $variableName): Type
}
}

$varExprString = '$' . $variableName;

if ($this->hasVariableType($variableName)->no()) {
throw new UndefinedVariableException($this, $variableName);
}

$varExprString = '$' . $variableName;
if (!array_key_exists($varExprString, $this->expressionTypes)) {
return new MixedType();
if (!$this->isGlobalVariable($variableName)) {
return new MixedType();
}

$superglobalType = new ArrayType(new BenevolentUnionType([new IntegerType(), new StringType()]), new MixedType(true));
$this->expressionTypes[$varExprString] = ExpressionTypeHolder::createYes(new Variable($variableName), $superglobalType);
$this->nativeExpressionTypes[$varExprString] = ExpressionTypeHolder::createYes(new Variable($variableName), $superglobalType);
}

return TypeUtils::resolveLateResolvableTypes($this->expressionTypes[$varExprString]->getType());
Expand Down Expand Up @@ -591,6 +602,11 @@ public function getMaybeDefinedVariables(): array
return $variables;
}

private function isGlobalVariable(string $variableName): bool
{
return in_array($variableName, self::SUPERGLOBAL_VARIABLES, true);
}

/** @api */
public function hasConstant(Name $name): bool
{
Expand Down
22 changes: 1 addition & 21 deletions src/Analyser/ScopeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,6 @@

namespace PHPStan\Analyser;

use PhpParser\Node\Expr\Variable;
use PHPStan\Type\ArrayType;
use PHPStan\Type\BenevolentUnionType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\MixedType;
use PHPStan\Type\StringType;

/**
* @api
*/
Expand All @@ -21,20 +14,7 @@ public function __construct(private InternalScopeFactory $internalScopeFactory)

public function create(ScopeContext $context): MutatingScope
{
$superglobalType = new ArrayType(new BenevolentUnionType([new IntegerType(), new StringType()]), new MixedType(true));
$expressionTypes = [
'$GLOBALS' => ExpressionTypeHolder::createYes(new Variable('GLOBALS'), $superglobalType),
'$_SERVER' => ExpressionTypeHolder::createYes(new Variable('_SERVER'), $superglobalType),
'$_GET' => ExpressionTypeHolder::createYes(new Variable('_GET'), $superglobalType),
'$_POST' => ExpressionTypeHolder::createYes(new Variable('_POST'), $superglobalType),
'$_FILES' => ExpressionTypeHolder::createYes(new Variable('_FILES'), $superglobalType),
'$_COOKIE' => ExpressionTypeHolder::createYes(new Variable('_COOKIE'), $superglobalType),
'$_SESSION' => ExpressionTypeHolder::createYes(new Variable('_SESSION'), $superglobalType),
'$_REQUEST' => ExpressionTypeHolder::createYes(new Variable('_REQUEST'), $superglobalType),
'$_ENV' => ExpressionTypeHolder::createYes(new Variable('_ENV'), $superglobalType),
];

return $this->internalScopeFactory->create($context, false, null, null, $expressionTypes, $expressionTypes);
return $this->internalScopeFactory->create($context);
}

}
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/ScopeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ public function testDefinedVariables(): void
->assignVariable('a', new ConstantStringType('a'), new StringType(), TrinaryLogic::createYes())
->assignVariable('b', new ConstantStringType('b'), new StringType(), TrinaryLogic::createMaybe());

$this->assertSame(['GLOBALS', '_SERVER', '_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_REQUEST', '_ENV', 'a'], $scope->getDefinedVariables());
$this->assertSame(['a'], $scope->getDefinedVariables());
}

public function testMaybeDefinedVariables(): void
Expand Down
20 changes: 10 additions & 10 deletions tests/PHPStan/Analyser/nsrt/get-defined-vars.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,37 +10,37 @@

function doFoo(int $param) {
$local = "foo";
assertType('array{GLOBALS: array<mixed>, _SERVER: array<mixed>, _GET: array<mixed>, _POST: array<mixed>, _FILES: array<mixed>, _COOKIE: array<mixed>, _SESSION: array<mixed>, _REQUEST: array<mixed>, _ENV: array<mixed>, param: int, local: \'foo\'}', get_defined_vars());
assertType('array{\'GLOBALS\', \'_SERVER\', \'_GET\', \'_POST\', \'_FILES\', \'_COOKIE\', \'_SESSION\', \'_REQUEST\', \'_ENV\', \'param\', \'local\'}', array_keys(get_defined_vars()));
assertType('array{param: int, local: \'foo\'}', get_defined_vars());
assertType('array{\'param\', \'local\'}', array_keys(get_defined_vars()));
}

function doBar(int $param) {
global $global;
$local = "foo";
assertType('array{GLOBALS: array<mixed>, _SERVER: array<mixed>, _GET: array<mixed>, _POST: array<mixed>, _FILES: array<mixed>, _COOKIE: array<mixed>, _SESSION: array<mixed>, _REQUEST: array<mixed>, _ENV: array<mixed>, param: int, global: mixed, local: \'foo\'}', get_defined_vars());
assertType('array{\'GLOBALS\', \'_SERVER\', \'_GET\', \'_POST\', \'_FILES\', \'_COOKIE\', \'_SESSION\', \'_REQUEST\', \'_ENV\', \'param\', \'global\', \'local\'}', array_keys(get_defined_vars()));
assertType('array{param: int, global: mixed, local: \'foo\'}', get_defined_vars());
assertType('array{\'param\', \'global\', \'local\'}', array_keys(get_defined_vars()));
}

function doConditional(int $param) {
$local = "foo";
if(true) {
$conditional = "bar";
assertType('array{GLOBALS: array<mixed>, _SERVER: array<mixed>, _GET: array<mixed>, _POST: array<mixed>, _FILES: array<mixed>, _COOKIE: array<mixed>, _SESSION: array<mixed>, _REQUEST: array<mixed>, _ENV: array<mixed>, param: int, local: \'foo\', conditional: \'bar\'}', get_defined_vars());
assertType('array{param: int, local: \'foo\', conditional: \'bar\'}', get_defined_vars());
} else {
$other = "baz";
assertType('array{GLOBALS: array<mixed>, _SERVER: array<mixed>, _GET: array<mixed>, _POST: array<mixed>, _FILES: array<mixed>, _COOKIE: array<mixed>, _SESSION: array<mixed>, _REQUEST: array<mixed>, _ENV: array<mixed>, param: int, local: \'foo\', other: \'baz\'}', get_defined_vars());
assertType('array{param: int, local: \'foo\', other: \'baz\'}', get_defined_vars());
}
assertType('array{GLOBALS: array<mixed>, _SERVER: array<mixed>, _GET: array<mixed>, _POST: array<mixed>, _FILES: array<mixed>, _COOKIE: array<mixed>, _SESSION: array<mixed>, _REQUEST: array<mixed>, _ENV: array<mixed>, param: int, local: \'foo\', conditional: \'bar\'}', get_defined_vars());
assertType('array{param: int, local: \'foo\', conditional: \'bar\'}', get_defined_vars());
}

function doRandom(int $param) {
$local = "foo";
if(rand(0, 1)) {
$random1 = "bar";
assertType('array{GLOBALS: array<mixed>, _SERVER: array<mixed>, _GET: array<mixed>, _POST: array<mixed>, _FILES: array<mixed>, _COOKIE: array<mixed>, _SESSION: array<mixed>, _REQUEST: array<mixed>, _ENV: array<mixed>, param: int, local: \'foo\', random1: \'bar\'}', get_defined_vars());
assertType('array{param: int, local: \'foo\', random1: \'bar\'}', get_defined_vars());
} else {
$random2 = "baz";
assertType('array{GLOBALS: array<mixed>, _SERVER: array<mixed>, _GET: array<mixed>, _POST: array<mixed>, _FILES: array<mixed>, _COOKIE: array<mixed>, _SESSION: array<mixed>, _REQUEST: array<mixed>, _ENV: array<mixed>, param: int, local: \'foo\', random2: \'baz\'}', get_defined_vars());
assertType('array{param: int, local: \'foo\', random2: \'baz\'}', get_defined_vars());
}
assertType('array{GLOBALS: array<mixed>, _SERVER: array<mixed>, _GET: array<mixed>, _POST: array<mixed>, _FILES: array<mixed>, _COOKIE: array<mixed>, _SESSION: array<mixed>, _REQUEST: array<mixed>, _ENV: array<mixed>, param: int, local: \'foo\', random2?: \'baz\', random1?: \'bar\'}', get_defined_vars());
assertType('array{param: int, local: \'foo\', random2?: \'baz\', random1?: \'bar\'}', get_defined_vars());
}
57 changes: 1 addition & 56 deletions tests/PHPStan/Rules/Debug/DebugScopeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,51 +21,14 @@ public function testRuleInPhpStanNamespace(): void
{
$this->analyse([__DIR__ . '/data/debug-scope.php'], [
[
implode("\n", [
'$GLOBALS (Yes): array<mixed>',
'$_SERVER (Yes): array<mixed>',
'$_GET (Yes): array<mixed>',
'$_POST (Yes): array<mixed>',
'$_FILES (Yes): array<mixed>',
'$_COOKIE (Yes): array<mixed>',
'$_SESSION (Yes): array<mixed>',
'$_REQUEST (Yes): array<mixed>',
'$_ENV (Yes): array<mixed>',
'native $GLOBALS (Yes): array<mixed>',
'native $_SERVER (Yes): array<mixed>',
'native $_GET (Yes): array<mixed>',
'native $_POST (Yes): array<mixed>',
'native $_FILES (Yes): array<mixed>',
'native $_COOKIE (Yes): array<mixed>',
'native $_SESSION (Yes): array<mixed>',
'native $_REQUEST (Yes): array<mixed>',
'native $_ENV (Yes): array<mixed>',
]),
'Scope is empty',
7,
],
[
implode("\n", [
'$GLOBALS (Yes): array<mixed>',
'$_SERVER (Yes): array<mixed>',
'$_GET (Yes): array<mixed>',
'$_POST (Yes): array<mixed>',
'$_FILES (Yes): array<mixed>',
'$_COOKIE (Yes): array<mixed>',
'$_SESSION (Yes): array<mixed>',
'$_REQUEST (Yes): array<mixed>',
'$_ENV (Yes): array<mixed>',
'$a (Yes): int',
'$b (Yes): int',
'$debug (Yes): bool',
'native $GLOBALS (Yes): array<mixed>',
'native $_SERVER (Yes): array<mixed>',
'native $_GET (Yes): array<mixed>',
'native $_POST (Yes): array<mixed>',
'native $_FILES (Yes): array<mixed>',
'native $_COOKIE (Yes): array<mixed>',
'native $_SESSION (Yes): array<mixed>',
'native $_REQUEST (Yes): array<mixed>',
'native $_ENV (Yes): array<mixed>',
'native $a (Yes): int',
'native $b (Yes): int',
'native $debug (Yes): bool',
Expand All @@ -74,28 +37,10 @@ public function testRuleInPhpStanNamespace(): void
],
[
implode("\n", [
'$GLOBALS (Yes): array<mixed>',
'$_SERVER (Yes): array<mixed>',
'$_GET (Yes): array<mixed>',
'$_POST (Yes): array<mixed>',
'$_FILES (Yes): array<mixed>',
'$_COOKIE (Yes): array<mixed>',
'$_SESSION (Yes): array<mixed>',
'$_REQUEST (Yes): array<mixed>',
'$_ENV (Yes): array<mixed>',
'$a (Yes): int',
'$b (Yes): int',
'$debug (Yes): bool',
'$c (Maybe): 1',
'native $GLOBALS (Yes): array<mixed>',
'native $_SERVER (Yes): array<mixed>',
'native $_GET (Yes): array<mixed>',
'native $_POST (Yes): array<mixed>',
'native $_FILES (Yes): array<mixed>',
'native $_COOKIE (Yes): array<mixed>',
'native $_SESSION (Yes): array<mixed>',
'native $_REQUEST (Yes): array<mixed>',
'native $_ENV (Yes): array<mixed>',
'native $a (Yes): int',
'native $b (Yes): int',
'native $debug (Yes): bool',
Expand Down

0 comments on commit 86901a7

Please sign in to comment.