Add Safe PHP functions

This commit is contained in:
Alex Cabal 2019-03-07 12:11:50 -06:00
parent 04a956886a
commit 58cc098058
260 changed files with 49458 additions and 45 deletions

View file

@ -0,0 +1,43 @@
<?php
namespace TheCodingMachine\Safe\PHPStan\Rules;
use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Rules\Rule;
use PHPStan\ShouldNotHappenException;
use TheCodingMachine\Safe\PHPStan\Utils\FunctionListLoader;
/**
* This rule checks that no superglobals are used in code.
*/
class UseSafeFunctionsRule implements Rule
{
public function getNodeType(): string
{
return Node\Expr\FuncCall::class;
}
/**
* @param Node\Expr\FuncCall $node
* @param \PHPStan\Analyser\Scope $scope
* @return string[]
*/
public function processNode(Node $node, Scope $scope): array
{
if (!$node->name instanceof Node\Name) {
return [];
}
$functionName = $node->name->toString();
$unsafeFunctions = FunctionListLoader::getFunctionList();
if (isset($unsafeFunctions[$functionName])) {
return ["Function $functionName is unsafe to use. It can return FALSE instead of throwing an exception. Please add 'use function Safe\\$functionName;' at the beginning of the file to use the variant provided by the 'thecodingmachine/safe' library."];
}
return [];
}
}

View file

@ -0,0 +1,79 @@
<?php declare(strict_types = 1);
namespace TheCodingMachine\Safe\PHPStan\Type\Php;
use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\ArrayType;
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
use PHPStan\Type\MixedType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeUtils;
class ReplaceSafeFunctionsDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
{
/** @var array<string, int> */
private $functions = [
'Safe\preg_replace' => 2,
];
public function isFunctionSupported(FunctionReflection $functionReflection): bool
{
return array_key_exists($functionReflection->getName(), $this->functions);
}
public function getTypeFromFunctionCall(
FunctionReflection $functionReflection,
FuncCall $functionCall,
Scope $scope
): Type {
$type = $this->getPreliminarilyResolvedTypeFromFunctionCall($functionReflection, $functionCall, $scope);
$possibleTypes = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType();
if (TypeCombinator::containsNull($possibleTypes)) {
$type = TypeCombinator::addNull($type);
}
return $type;
}
private function getPreliminarilyResolvedTypeFromFunctionCall(
FunctionReflection $functionReflection,
FuncCall $functionCall,
Scope $scope
): Type {
$argumentPosition = $this->functions[$functionReflection->getName()];
if (count($functionCall->args) <= $argumentPosition) {
return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType();
}
$subjectArgumentType = $scope->getType($functionCall->args[$argumentPosition]->value);
$defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType();
if ($subjectArgumentType instanceof MixedType) {
return TypeUtils::toBenevolentUnion($defaultReturnType);
}
$stringType = new StringType();
$arrayType = new ArrayType(new MixedType(), new MixedType());
$isStringSuperType = $stringType->isSuperTypeOf($subjectArgumentType);
$isArraySuperType = $arrayType->isSuperTypeOf($subjectArgumentType);
$compareSuperTypes = $isStringSuperType->compareTo($isArraySuperType);
if ($compareSuperTypes === $isStringSuperType) {
return $stringType;
} elseif ($compareSuperTypes === $isArraySuperType) {
if ($subjectArgumentType instanceof ArrayType) {
return $subjectArgumentType->generalizeValues();
}
return $subjectArgumentType;
}
return $defaultReturnType;
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace TheCodingMachine\Safe\PHPStan\Utils;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\MethodReflection;
class FunctionListLoader
{
private static $functions;
/**
* @return string[]
*/
public static function getFunctionList(): array
{
if (self::$functions === null) {
if (\file_exists(__DIR__.'/../../../safe/generated/functionsList.php')) {
$functions = require __DIR__.'/../../../safe/generated/functionsList.php';
} elseif (\file_exists(__DIR__.'/../../vendor/thecodingmachine/safe/generated/functionsList.php')) {
$functions = require __DIR__.'/../../vendor/thecodingmachine/safe/generated/functionsList.php';
} else {
throw new \RuntimeException('Could not find thecodingmachine/safe\'s functionsList.php file.');
}
// Let's index these functions by their name
self::$functions = \Safe\array_combine($functions, $functions);
}
return self::$functions;
}
}