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 8a18dea commit 98f40eb
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 39 deletions.
98 changes: 65 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 @@ -2465,23 +2442,21 @@ public function enterClass(ClassReflection $classReflection): self
{
$thisHolder = ExpressionTypeHolder::createYes(new Variable('this'), new ThisType($classReflection));

$superglobals = $this->getSuperglobals();

return $this->scopeFactory->create(
$this->context->enterClass($classReflection),
$this->isDeclareStrictTypes(),
null,
$this->getNamespace(),
array_merge($this->getConstantTypes(), [
'$this' => $thisHolder,
]),
array_merge($superglobals, $this->getConstantTypes(), ['$this' => $thisHolder]),
[],
null,
null,
true,
[],
[],
array_merge($this->getNativeConstantTypes(), [
'$this' => $thisHolder,
]),
array_merge($superglobals, $this->getNativeConstantTypes(), ['$this' => $thisHolder]),
[],
false,
$classReflection->isAnonymous() ? $this : null,
Expand Down Expand Up @@ -2709,30 +2684,42 @@ private function enterFunctionLike(
$nativeExpressionTypes['$this'] = $this->nativeExpressionTypes['$this'];
}

$superglobals = $this->getSuperglobals();

return $this->scopeFactory->create(
$this->context,
$this->isDeclareStrictTypes(),
$functionReflection,
$this->getNamespace(),
array_merge($this->getConstantTypes(), $expressionTypes),
array_merge($superglobals, $this->getConstantTypes(), $expressionTypes),
[],
null,
null,
true,
[],
[],
array_merge($this->getNativeConstantTypes(), $nativeExpressionTypes),
array_merge($superglobals, $this->getNativeConstantTypes(), $nativeExpressionTypes),
);
}

/** @api */
public function enterNamespace(string $namespaceName): self
{
$superglobals = $this->getSuperglobals();

return $this->scopeFactory->create(
$this->context->beginFile(),
$this->isDeclareStrictTypes(),
null,
$namespaceName,
$superglobals,
[],
null,
null,
true,
[],
[],
$superglobals,
);
}

Expand Down Expand Up @@ -2931,19 +2918,21 @@ private function enterAnonymousFunctionWithoutReflection(
$nativeTypes['$this'] = ExpressionTypeHolder::createYes($node, $this->getNativeType($node));
}

$superglobals = $this->getSuperglobals();

return $this->scopeFactory->create(
$this->context,
$this->isDeclareStrictTypes(),
$this->getFunction(),
$this->getNamespace(),
array_merge($this->getConstantTypes(), $expressionTypes),
array_merge($superglobals, $this->getConstantTypes(), $expressionTypes),
[],
$this->inClosureBindScopeClass,
new TrivialParametersAcceptor(),
true,
[],
[],
array_merge($this->getNativeConstantTypes(), $nativeTypes),
array_merge($superglobals, $this->getNativeConstantTypes(), $nativeTypes),
[],
false,
$this,
Expand Down Expand Up @@ -5008,4 +4997,47 @@ private function getNativeConstantTypes(): array
return $constantTypes;
}

/** @return array<string, ExpressionTypeHolder> */
public function getSuperglobals(): array
{
$superglobalType = new ArrayType(new StringType(), new MixedType($this->explicitMixedForGlobalVariables));

return [
'$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),
];
}

public function addSuperglobals(): self
{
$expressionTypes = $this->expressionTypes;
$nativeExpressionTypes = $this->nativeExpressionTypes;
foreach ($this->getSuperglobals() as $exprStr => $typeHolder) {
$expressionTypes[$exprStr] = $typeHolder;
$nativeExpressionTypes[$exprStr] = $typeHolder;
}

return $this->scopeFactory->create(
$this->context,
$this->declareStrictTypes,
$this->function,
$this->namespace,
$expressionTypes,
$this->conditionalExpressions,
$this->inClosureBindScopeClass,
$this->anonymousFunctionReflection,
$this->inFirstLevelStatement,
$this->currentlyAssignedExpressions,
$this->currentlyAllowedUndefinedExpressions,
$nativeExpressionTypes,
);
}

}
2 changes: 1 addition & 1 deletion src/Analyser/ScopeFactory.php
Expand Up @@ -12,7 +12,7 @@ public function __construct(private InternalScopeFactory $internalScopeFactory)

public function create(ScopeContext $context): MutatingScope
{
return $this->internalScopeFactory->create($context);
return $this->internalScopeFactory->create($context)->addSuperglobals();
}

}
2 changes: 1 addition & 1 deletion src/Testing/TypeInferenceTestCase.php
Expand Up @@ -65,7 +65,7 @@ public function processFile(
$resolver->setAnalysedFiles(array_map(static fn (string $file): string => $fileHelper->normalizePath($file), array_merge([$file], $this->getAdditionalAnalysedFiles())));

$scopeFactory = $this->createScopeFactory($reflectionProvider, $typeSpecifier, $dynamicConstantNames);
$scope = $scopeFactory->create(ScopeContext::create($file));
$scope = $scopeFactory->create(ScopeContext::create($file))->addSuperglobals();

$resolver->processNodes(
$this->getParser()->parseFile($file),
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 98f40eb

Please sign in to comment.