Skip to content

Commit

Permalink
Add superglobal variable expressions into scope by default
Browse files Browse the repository at this point in the history
  • Loading branch information
herndlm committed Nov 20, 2022
1 parent afbb56e commit 5288067
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 39 deletions.
2 changes: 2 additions & 0 deletions conf/config.neon
Expand Up @@ -598,6 +598,8 @@ services:

-
class: PHPStan\Analyser\ScopeFactory
arguments:
explicitMixedForGlobalVariables: %featureToggles.explicitMixedForGlobalVariables%

-
class: PHPStan\Analyser\NodeScopeResolver
Expand Down
62 changes: 29 additions & 33 deletions src/Analyser/MutatingScope.php
Expand Up @@ -467,10 +467,6 @@ 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 @@ -507,10 +503,6 @@ public function getVariableType(string $variableName): Type

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

return new MixedType();
}

Expand Down Expand Up @@ -538,21 +530,6 @@ public function getDefinedVariables(): array
return $variables;
}

private function isGlobalVariable(string $variableName): bool
{
return in_array($variableName, [
'GLOBALS',
'_SERVER',
'_GET',
'_POST',
'_FILES',
'_COOKIE',
'_SESSION',
'_REQUEST',
'_ENV',
], true);
}

/** @api */
public function hasConstant(Name $name): bool
{
Expand Down Expand Up @@ -2470,18 +2447,14 @@ public function enterClass(ClassReflection $classReflection): self
$this->isDeclareStrictTypes(),
null,
$this->getNamespace(),
array_merge($this->getConstantTypes(), [
'$this' => $thisHolder,
]),
array_merge($this->getSuperglobalExpressionTypes(), $this->getConstantTypes(), ['$this' => $thisHolder]),
[],
null,
null,
true,
[],
[],
array_merge($this->getNativeConstantTypes(), [
'$this' => $thisHolder,
]),
array_merge($this->getSuperglobalExpressionTypes(), $this->getNativeConstantTypes(), ['$this' => $thisHolder]),
[],
false,
$classReflection->isAnonymous() ? $this : null,
Expand Down Expand Up @@ -2714,14 +2687,14 @@ private function enterFunctionLike(
$this->isDeclareStrictTypes(),
$functionReflection,
$this->getNamespace(),
array_merge($this->getConstantTypes(), $expressionTypes),
array_merge($this->getSuperglobalExpressionTypes(), $this->getConstantTypes(), $expressionTypes),
[],
null,
null,
true,
[],
[],
array_merge($this->getNativeConstantTypes(), $nativeExpressionTypes),
array_merge($this->getSuperglobalExpressionTypes(), $this->getNativeConstantTypes(), $nativeExpressionTypes),
);
}

Expand All @@ -2733,6 +2706,14 @@ public function enterNamespace(string $namespaceName): self
$this->isDeclareStrictTypes(),
null,
$namespaceName,
$this->getSuperglobalExpressionTypes(),
[],
null,
null,
true,
[],
[],
$this->getSuperglobalExpressionTypes(),
);
}

Expand Down Expand Up @@ -2936,14 +2917,14 @@ private function enterAnonymousFunctionWithoutReflection(
$this->isDeclareStrictTypes(),
$this->getFunction(),
$this->getNamespace(),
array_merge($this->getConstantTypes(), $expressionTypes),
array_merge($this->getSuperglobalExpressionTypes(), $this->getConstantTypes(), $expressionTypes),
[],
$this->inClosureBindScopeClass,
new TrivialParametersAcceptor(),
true,
[],
[],
array_merge($this->getNativeConstantTypes(), $nativeTypes),
array_merge($this->getSuperglobalExpressionTypes(), $this->getNativeConstantTypes(), $nativeTypes),
[],
false,
$this,
Expand Down Expand Up @@ -5008,4 +4989,19 @@ private function getNativeConstantTypes(): array
return $constantTypes;
}

/** @return array<string, ExpressionTypeHolder> */
private function getSuperglobalExpressionTypes(): array
{
$superglobalExpressionTypes = [];
$exprStrings = ['$GLOBALS', '$_SERVER', '$_GET', '$_POST', '$_FILES', '$_COOKIE', '$_SESSION', '$_REQUEST', '$_ENV'];
foreach ($this->expressionTypes as $exprString => $typeHolder) {
if (!in_array($exprString, $exprStrings, true)) {
continue;
}

$superglobalExpressionTypes[$exprString] = $typeHolder;
}
return $superglobalExpressionTypes;
}

}
31 changes: 29 additions & 2 deletions src/Analyser/ScopeFactory.php
Expand Up @@ -2,17 +2,44 @@

namespace PHPStan\Analyser;

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

/** @api */
class ScopeFactory
{

public function __construct(private InternalScopeFactory $internalScopeFactory)
public function __construct(
private InternalScopeFactory $internalScopeFactory,
private bool $explicitMixedForGlobalVariables,
)
{
}

public function create(ScopeContext $context): MutatingScope
{
return $this->internalScopeFactory->create($context);
$superglobalType = new ArrayType(new StringType(), new MixedType($this->explicitMixedForGlobalVariables));
$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,
);
}

}
1 change: 1 addition & 0 deletions src/Testing/PHPStanTestCase.php
Expand Up @@ -181,6 +181,7 @@ public function createScopeFactory(ReflectionProvider $reflectionProvider, TypeS
$container->getParameter('featureToggles')['explicitMixedForGlobalVariables'],
$constantResolver,
),
$container->getParameter('featureToggles')['explicitMixedForGlobalVariables'],
);
}

Expand Down
32 changes: 28 additions & 4 deletions tests/PHPStan/Analyser/data/superglobals.php
Expand Up @@ -2,6 +2,7 @@

namespace Superglobals;

use function PHPStan\Testing\assertNativeType;
use function PHPStan\Testing\assertType;

class Superglobals
Expand All @@ -22,14 +23,37 @@ public function originalTypes(): void

public function canBeOverwritten(): void
{
$_SESSION = [];
assertType('array{}', $_SESSION);
$GLOBALS = [];
assertType('array{}', $GLOBALS);
assertNativeType('array{}', $GLOBALS);
}

public function canBePartlyOverwritten(): void
{
$_SESSION['foo'] = 'foo';
assertType("non-empty-array<string, mixed>&hasOffsetValue('foo', 'foo')", $_SESSION);
$GLOBALS['foo'] = 'foo';
assertType("non-empty-array<string, mixed>&hasOffsetValue('foo', 'foo')", $GLOBALS);
assertNativeType("non-empty-array<string, mixed>&hasOffsetValue('foo', 'foo')", $GLOBALS);
}

public function canBeNarrowed(): void
{
if (isset($GLOBALS['foo'])) {
assertType("array<string, mixed>&hasOffsetValue('foo', mixed~null)", $GLOBALS);
assertNativeType("array<string, mixed>&hasOffset('foo')", $GLOBALS); // https://github.com/phpstan/phpstan/issues/8395
} else {
assertType('array<string, mixed>', $GLOBALS);
assertNativeType('array<string, mixed>', $GLOBALS);
}
assertType('array<string, mixed>', $GLOBALS);
assertNativeType('array<string, mixed>', $GLOBALS);
}

}

function functionScope() {
assertType('array<string, mixed>', $GLOBALS);
assertNativeType('array<string, mixed>', $GLOBALS);
}

assertType('array<string, mixed>', $GLOBALS);
assertNativeType('array<string, mixed>', $GLOBALS);

0 comments on commit 5288067

Please sign in to comment.