Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 1
<?php declare(strict_types = 1);
2
 
3
namespace Composer\Pcre\PHPStan;
4
 
5
use Composer\Pcre\Preg;
6
use Composer\Pcre\Regex;
7
use Composer\Pcre\PcreException;
8
use Nette\Utils\RegexpException;
9
use Nette\Utils\Strings;
10
use PhpParser\Node;
11
use PhpParser\Node\Expr\StaticCall;
12
use PhpParser\Node\Name\FullyQualified;
13
use PHPStan\Analyser\Scope;
14
use PHPStan\Rules\Rule;
15
use PHPStan\Rules\RuleErrorBuilder;
16
use function in_array;
17
use function sprintf;
18
 
19
/**
20
 * Copy of PHPStan's RegularExpressionPatternRule
21
 *
22
 * @implements Rule<StaticCall>
23
 */
24
class InvalidRegexPatternRule implements Rule
25
{
26
    public function getNodeType(): string
27
    {
28
        return StaticCall::class;
29
    }
30
 
31
    public function processNode(Node $node, Scope $scope): array
32
    {
33
        $patterns = $this->extractPatterns($node, $scope);
34
 
35
        $errors = [];
36
        foreach ($patterns as $pattern) {
37
            $errorMessage = $this->validatePattern($pattern);
38
            if ($errorMessage === null) {
39
                continue;
40
            }
41
 
42
            $errors[] = RuleErrorBuilder::message(sprintf('Regex pattern is invalid: %s', $errorMessage))->identifier('regexp.pattern')->build();
43
        }
44
 
45
        return $errors;
46
    }
47
 
48
    /**
49
     * @return string[]
50
     */
51
    private function extractPatterns(StaticCall $node, Scope $scope): array
52
    {
53
        if (!$node->class instanceof FullyQualified) {
54
            return [];
55
        }
56
        $isRegex = $node->class->toString() === Regex::class;
57
        $isPreg = $node->class->toString() === Preg::class;
58
        if (!$isRegex && !$isPreg) {
59
            return [];
60
        }
61
        if (!$node->name instanceof Node\Identifier || !Preg::isMatch('{^(match|isMatch|grep|replace|split)}', $node->name->name)) {
62
            return [];
63
        }
64
 
65
        $functionName = $node->name->name;
66
        if (!isset($node->getArgs()[0])) {
67
            return [];
68
        }
69
 
70
        $patternNode = $node->getArgs()[0]->value;
71
        $patternType = $scope->getType($patternNode);
72
 
73
        $patternStrings = [];
74
 
75
        foreach ($patternType->getConstantStrings() as $constantStringType) {
76
            if ($functionName === 'replaceCallbackArray') {
77
                continue;
78
            }
79
 
80
            $patternStrings[] = $constantStringType->getValue();
81
        }
82
 
83
        foreach ($patternType->getConstantArrays() as $constantArrayType) {
84
            if (
85
                in_array($functionName, [
86
                    'replace',
87
                    'replaceCallback',
88
                ], true)
89
            ) {
90
                foreach ($constantArrayType->getValueTypes() as $arrayKeyType) {
91
                    foreach ($arrayKeyType->getConstantStrings() as $constantString) {
92
                        $patternStrings[] = $constantString->getValue();
93
                    }
94
                }
95
            }
96
 
97
            if ($functionName !== 'replaceCallbackArray') {
98
                continue;
99
            }
100
 
101
            foreach ($constantArrayType->getKeyTypes() as $arrayKeyType) {
102
                foreach ($arrayKeyType->getConstantStrings() as $constantString) {
103
                    $patternStrings[] = $constantString->getValue();
104
                }
105
            }
106
        }
107
 
108
        return $patternStrings;
109
    }
110
 
111
    private function validatePattern(string $pattern): ?string
112
    {
113
        try {
114
            $msg = null;
115
            $prev = set_error_handler(function (int $severity, string $message, string $file) use (&$msg): bool {
116
                $msg = preg_replace("#^preg_match(_all)?\\(.*?\\): #", '', $message);
117
 
118
                return true;
119
            });
120
 
121
            if ($pattern === '') {
122
                return 'Empty string is not a valid regular expression';
123
            }
124
 
125
            Preg::match($pattern, '');
126
            if ($msg !== null) {
127
                return $msg;
128
            }
129
        } catch (PcreException $e) {
130
            if ($e->getCode() === PREG_INTERNAL_ERROR && $msg !== null) {
131
                return $msg;
132
            }
133
 
134
            return preg_replace('{.*? failed executing ".*": }', '', $e->getMessage());
135
        } finally {
136
            restore_error_handler();
137
        }
138
 
139
        return null;
140
    }
141
 
142
}