Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions src/Analyser/ExprHandler/ShellExecHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser\ExprHandler;

use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ShellExec;
use PhpParser\Node\Stmt;
use PHPStan\Analyser\ExpressionContext;
use PHPStan\Analyser\ExpressionResult;
use PHPStan\Analyser\ExpressionResultFactory;
use PHPStan\Analyser\ExpressionResultStorage;
use PHPStan\Analyser\ExprHandler;
use PHPStan\Analyser\ExprHandler\Helper\ImplicitToStringCallHelper;
use PHPStan\Analyser\MutatingScope;
use PHPStan\Analyser\NodeScopeResolver;
use PHPStan\Analyser\Scope;
use PHPStan\Analyser\SpecifiedTypes;
use PHPStan\Analyser\TypeSpecifier;
use PHPStan\Analyser\TypeSpecifierContext;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\NullType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use function array_merge;

/**
* The backtick operator executes a shell command and returns its output, like
* shell_exec(): string|false|null. Its interpolated parts are walked so their
* expressions are processed and rules (e.g. BacktickRule) can read them.
*
* @implements ExprHandler<ShellExec>
*/
#[AutowiredService]
final class ShellExecHandler implements ExprHandler
{

public function __construct(
private ImplicitToStringCallHelper $implicitToStringCallHelper,
private ExpressionResultFactory $expressionResultFactory,
)
{
}

public function supports(Expr $expr): bool
{
return $expr instanceof ShellExec;
}

public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult
{
$beforeScope = $scope;
$hasYield = false;
$throwPoints = [];
$impurePoints = [];
$isAlwaysTerminating = false;
foreach ($expr->parts as $part) {
if (!$part instanceof Expr) {
continue;
}
$partResult = $nodeScopeResolver->processExprNode($stmt, $part, $scope, $storage, $nodeCallback, $context->enterDeep());
$hasYield = $hasYield || $partResult->hasYield();
$throwPoints = array_merge($throwPoints, $partResult->getThrowPoints());
$impurePoints = array_merge($impurePoints, $partResult->getImpurePoints());

$toStringResult = $this->implicitToStringCallHelper->processImplicitToStringCall($part, $scope);
$throwPoints = array_merge($throwPoints, $toStringResult->getThrowPoints());
$impurePoints = array_merge($impurePoints, $toStringResult->getImpurePoints());

$isAlwaysTerminating = $isAlwaysTerminating || $partResult->isAlwaysTerminating();
$scope = $partResult->getScope();
}

return $this->expressionResultFactory->create(
$scope,
beforeScope: $beforeScope,
expr: $expr,
hasYield: $hasYield,
isAlwaysTerminating: $isAlwaysTerminating,
throwPoints: $throwPoints,
impurePoints: $impurePoints,
);
}

public function resolveType(MutatingScope $scope, Expr $expr): Type
{
return TypeCombinator::union(new StringType(), new ConstantBooleanType(false), new NullType());
}

public function specifyTypes(TypeSpecifier $typeSpecifier, Scope $scope, Expr $expr, TypeSpecifierContext $context): SpecifiedTypes
{
return $typeSpecifier->specifyDefaultTypes($scope, $expr, $context);
}

}
71 changes: 71 additions & 0 deletions src/Analyser/ExprHandler/Virtual/IssetExprHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser\ExprHandler\Virtual;

use PhpParser\Node\Expr;
use PhpParser\Node\Stmt;
use PHPStan\Analyser\ExpressionContext;
use PHPStan\Analyser\ExpressionResult;
use PHPStan\Analyser\ExpressionResultFactory;
use PHPStan\Analyser\ExpressionResultStorage;
use PHPStan\Analyser\ExprHandler;
use PHPStan\Analyser\MutatingScope;
use PHPStan\Analyser\NodeScopeResolver;
use PHPStan\Analyser\Scope;
use PHPStan\Analyser\SpecifiedTypes;
use PHPStan\Analyser\TypeSpecifier;
use PHPStan\Analyser\TypeSpecifierContext;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Node\IssetExpr;
use PHPStan\Type\Type;

/**
* IssetExpr is a certainty marker wrapped around an isset-tested expression so
* a type specification can reduce that expression's existence certainty (to
* maybe / unset) instead of narrowing its type. The specifications carrying it
* read only its certainty, never its type - so the marker just reports its
* inner expression's type, which lets it be priced like any other node rather
* than being a special case in the resolution paths.
*
* @implements ExprHandler<IssetExpr>
*/
#[AutowiredService]
final class IssetExprHandler implements ExprHandler
{

public function __construct(private ExpressionResultFactory $expressionResultFactory)
{
}

public function supports(Expr $expr): bool
{
return $expr instanceof IssetExpr;
}

public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult
{
// a virtual node handler - the caller will only be interested in the
// type; the inner expr is not processed, its type is just reported

return $this->expressionResultFactory->create(
$scope,
beforeScope: $scope,
expr: $expr,
hasYield: false,
isAlwaysTerminating: false,
throwPoints: [],
impurePoints: [],
);
}

public function resolveType(MutatingScope $scope, Expr $expr): Type
{
return $scope->getType($expr->getExpr());
}

public function specifyTypes(TypeSpecifier $typeSpecifier, Scope $scope, Expr $expr, TypeSpecifierContext $context): SpecifiedTypes
{
return $typeSpecifier->specifyDefaultTypes($scope, $expr, $context);
}

}
Loading