123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112 |
- <?php declare(strict_types=1);
- namespace Composer\Pcre\PHPStan;
- use Composer\Pcre\Preg;
- use Composer\Pcre\Regex;
- use PhpParser\Node;
- use PhpParser\Node\Expr\StaticCall;
- use PhpParser\Node\Name\FullyQualified;
- use PHPStan\Analyser\Scope;
- use PHPStan\Analyser\SpecifiedTypes;
- use PHPStan\Rules\Rule;
- use PHPStan\Rules\RuleErrorBuilder;
- use PHPStan\TrinaryLogic;
- use PHPStan\Type\ObjectType;
- use PHPStan\Type\Type;
- use PHPStan\Type\TypeCombinator;
- use PHPStan\Type\Php\RegexArrayShapeMatcher;
- use function sprintf;
- /**
- * @implements Rule<StaticCall>
- */
- final class UnsafeStrictGroupsCallRule implements Rule
- {
- /**
- * @var RegexArrayShapeMatcher
- */
- private $regexShapeMatcher;
- public function __construct(RegexArrayShapeMatcher $regexShapeMatcher)
- {
- $this->regexShapeMatcher = $regexShapeMatcher;
- }
- public function getNodeType(): string
- {
- return StaticCall::class;
- }
- public function processNode(Node $node, Scope $scope): array
- {
- if (!$node->class instanceof FullyQualified) {
- return [];
- }
- $isRegex = $node->class->toString() === Regex::class;
- $isPreg = $node->class->toString() === Preg::class;
- if (!$isRegex && !$isPreg) {
- return [];
- }
- if (!$node->name instanceof Node\Identifier || !in_array($node->name->name, ['matchStrictGroups', 'isMatchStrictGroups', 'matchAllStrictGroups', 'isMatchAllStrictGroups'], true)) {
- return [];
- }
- $args = $node->getArgs();
- if (!isset($args[0])) {
- return [];
- }
- $patternArg = $args[0] ?? null;
- if ($isPreg) {
- if (!isset($args[2])) { // no matches set, skip as the matches won't be used anyway
- return [];
- }
- $flagsArg = $args[3] ?? null;
- } else {
- $flagsArg = $args[2] ?? null;
- }
- if ($patternArg === null) {
- return [];
- }
- $flagsType = PregMatchFlags::getType($flagsArg, $scope);
- if ($flagsType === null) {
- return [];
- }
- $matchedType = $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createYes(), $scope);
- if ($matchedType === null) {
- return [
- RuleErrorBuilder::message(sprintf('The %s call is potentially unsafe as $matches\' type could not be inferred.', $node->name->name))
- ->identifier('composerPcre.maybeUnsafeStrictGroups')
- ->build(),
- ];
- }
- if (count($matchedType->getConstantArrays()) === 1) {
- $matchedType = $matchedType->getConstantArrays()[0];
- $nullableGroups = [];
- foreach ($matchedType->getValueTypes() as $index => $type) {
- if (TypeCombinator::containsNull($type)) {
- $nullableGroups[] = $matchedType->getKeyTypes()[$index]->getValue();
- }
- }
- if (\count($nullableGroups) > 0) {
- return [
- RuleErrorBuilder::message(sprintf(
- 'The %s call is unsafe as match group%s "%s" %s optional and may be null.',
- $node->name->name,
- \count($nullableGroups) > 1 ? 's' : '',
- implode('", "', $nullableGroups),
- \count($nullableGroups) > 1 ? 'are' : 'is'
- ))->identifier('composerPcre.unsafeStrictGroups')->build(),
- ];
- }
- }
- return [];
- }
- }
|