Issues (138)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

SourceStubber/PhpStormStubsSourceStubber.php (5 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Roave\BetterReflection\SourceLocator\SourceStubber;
6
7
use JetBrains\PHPStormStub\PhpStormStubsMap;
8
use PhpParser\BuilderHelpers;
9
use PhpParser\Node;
10
use PhpParser\NodeTraverser;
11
use PhpParser\NodeVisitor\NameResolver;
12
use PhpParser\NodeVisitorAbstract;
13
use PhpParser\Parser;
14
use PhpParser\PrettyPrinter\Standard;
15
use Roave\BetterReflection\Reflection\Exception\InvalidConstantNode;
16
use Roave\BetterReflection\SourceLocator\FileChecker;
17
use Roave\BetterReflection\SourceLocator\SourceStubber\Exception\CouldNotFindPhpStormStubs;
18
use Roave\BetterReflection\Util\ConstantNodeChecker;
19
use Traversable;
20
use function array_key_exists;
21
use function assert;
22
use function constant;
23
use function count;
24
use function defined;
25
use function explode;
26
use function file_get_contents;
27
use function in_array;
28
use function is_dir;
29
use function is_string;
30
use function sprintf;
31
use function str_replace;
32
use function strtolower;
33
34
/**
35
 * @internal
36
 */
37
final class PhpStormStubsSourceStubber implements SourceStubber
38
{
39
    private const BUILDER_OPTIONS    = ['shortArraySyntax' => true];
40
    private const SEARCH_DIRECTORIES = [
41
        __DIR__ . '/../../../../../jetbrains/phpstorm-stubs',
42
        __DIR__ . '/../../../vendor/jetbrains/phpstorm-stubs',
43
    ];
44
45
    /** @var Parser */
46
    private $phpParser;
47
48
    /** @var Standard */
49
    private $prettyPrinter;
50
51
    /** @var NodeTraverser */
52
    private $nodeTraverser;
53
54
    /** @var string|null */
55
    private $stubsDirectory;
56
57
    /** @var NodeVisitorAbstract */
58
    private $cachingVisitor;
59
60
    /** @var array<string, Node\Stmt\ClassLike> */
61
    private $classNodes = [];
62
63
    /** @var array<string, Node\Stmt\Function_> */
64
    private $functionNodes = [];
65
66
    /** @var array<string, Node\Stmt\Const_|Node\Expr\FuncCall> */
67
    private $constantNodes = [];
68
69 1182
    public function __construct(Parser $phpParser)
70
    {
71 1182
        $this->phpParser     = $phpParser;
72 1182
        $this->prettyPrinter = new Standard(self::BUILDER_OPTIONS);
73
74 1182
        $this->cachingVisitor = $this->createCachingVisitor();
75
76 1182
        $this->nodeTraverser = new NodeTraverser();
77 1182
        $this->nodeTraverser->addVisitor(new NameResolver());
78 1182
        $this->nodeTraverser->addVisitor($this->cachingVisitor);
79 1182
    }
80
81 81
    public function generateClassStub(string $className) : ?StubData
82
    {
83 81
        if (! array_key_exists($className, PhpStormStubsMap::CLASSES)) {
84 1
            return null;
85
        }
86
87 80
        $filePath = PhpStormStubsMap::CLASSES[$className];
88
89 80
        if (! array_key_exists($className, $this->classNodes)) {
90 80
            $this->parseFile($filePath);
91
        }
92
93 80
        $stub = $this->createStub($this->classNodes[$className]);
94
95 80
        if ($className === Traversable::class) {
96
            // See https://github.com/JetBrains/phpstorm-stubs/commit/0778a26992c47d7dbee4d0b0bfb7fad4344371b1#diff-575bacb45377d474336c71cbf53c1729
97 43
            $stub = str_replace(' extends \iterable', '', $stub);
98
        }
99
100 80
        return new StubData($stub, $this->getExtensionFromFilePath($filePath));
101
    }
102
103 622
    public function generateFunctionStub(string $functionName) : ?StubData
104
    {
105 622
        if (! array_key_exists($functionName, PhpStormStubsMap::FUNCTIONS)) {
106 1
            return null;
107
        }
108
109 621
        $filePath = PhpStormStubsMap::FUNCTIONS[$functionName];
110
111 621
        if (! array_key_exists($functionName, $this->functionNodes)) {
112 621
            $this->parseFile($filePath);
113
        }
114
115 621
        return new StubData($this->createStub($this->functionNodes[$functionName]), $this->getExtensionFromFilePath($filePath));
116
    }
117
118 479
    public function generateConstantStub(string $constantName) : ?StubData
119
    {
120
        // https://github.com/JetBrains/phpstorm-stubs/pull/591
121 479
        if (in_array($constantName, ['TRUE', 'FALSE', 'NULL'], true)) {
122 3
            $constantName = strtolower($constantName);
123
        }
124
125 479
        if (! array_key_exists($constantName, PhpStormStubsMap::CONSTANTS)) {
126 1
            return null;
127
        }
128
129 478
        $filePath = PhpStormStubsMap::CONSTANTS[$constantName];
130
131 478
        if (! array_key_exists($constantName, $this->constantNodes)) {
132 478
            $this->parseFile($filePath);
133
        }
134
135 478
        return new StubData($this->createStub($this->constantNodes[$constantName]), $this->getExtensionFromFilePath($filePath));
136
    }
137
138 1179
    private function parseFile(string $filePath) : void
139
    {
140 1179
        $absoluteFilePath = $this->getAbsoluteFilePath($filePath);
141 1179
        FileChecker::assertReadableFile($absoluteFilePath);
142
143 1179
        $ast = $this->phpParser->parse(file_get_contents($absoluteFilePath));
144
145
        /** @psalm-suppress UndefinedMethod */
146 1179
        $this->cachingVisitor->clearNodes();
0 ignored issues
show
It seems like you code against a specific sub-type and not the parent class PhpParser\NodeVisitorAbstract as the method clearNodes() does only exist in the following sub-classes of PhpParser\NodeVisitorAbstract: Roave\BetterReflection\S...tubsSourceStubber.php$0. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
147
148 1179
        $this->nodeTraverser->traverse($ast);
0 ignored issues
show
It seems like $ast defined by $this->phpParser->parse(...nts($absoluteFilePath)) on line 143 can also be of type null; however, PhpParser\NodeTraverser::traverse() does only seem to accept array<integer,object<PhpParser\Node>>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
149
150
        /**
151
         * @psalm-suppress UndefinedMethod
152
         */
153 1179
        foreach ($this->cachingVisitor->getClassNodes() as $className => $classNode) {
0 ignored issues
show
It seems like you code against a specific sub-type and not the parent class PhpParser\NodeVisitorAbstract as the method getClassNodes() does only exist in the following sub-classes of PhpParser\NodeVisitorAbstract: Roave\BetterReflection\S...tubsSourceStubber.php$0. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
154 181
            assert(is_string($className));
155 181
            assert($classNode instanceof Node\Stmt\ClassLike);
156 181
            $this->classNodes[$className] = $classNode;
157
        }
158
159
        /**
160
         * @psalm-suppress UndefinedMethod
161
         */
162 1179
        foreach ($this->cachingVisitor->getFunctionNodes() as $functionName => $functionNode) {
0 ignored issues
show
It seems like you code against a specific sub-type and not the parent class PhpParser\NodeVisitorAbstract as the method getFunctionNodes() does only exist in the following sub-classes of PhpParser\NodeVisitorAbstract: Roave\BetterReflection\S...tubsSourceStubber.php$0. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
163 680
            assert(is_string($functionName));
164 680
            assert($functionNode instanceof Node\Stmt\Function_);
165 680
            $this->functionNodes[$functionName] = $functionNode;
166
        }
167
168
        /**
169
         * @psalm-suppress UndefinedMethod
170
         */
171 1179
        foreach ($this->cachingVisitor->getConstantNodes() as $constantName => $constantNode) {
0 ignored issues
show
It seems like you code against a specific sub-type and not the parent class PhpParser\NodeVisitorAbstract as the method getConstantNodes() does only exist in the following sub-classes of PhpParser\NodeVisitorAbstract: Roave\BetterReflection\S...tubsSourceStubber.php$0. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
172 605
            assert(is_string($constantName));
173 605
            assert($constantNode instanceof Node\Stmt\Const_ || $constantNode instanceof Node\Expr\FuncCall);
174 605
            $this->constantNodes[$constantName] = $constantNode;
175
        }
176 1179
    }
177
178 1179
    private function createStub(Node $node) : string
179
    {
180 1179
        return "<?php\n\n" . $this->prettyPrinter->prettyPrint([$node]) . ($node instanceof Node\Expr\FuncCall ? ';' : '') . "\n";
181
    }
182
183
    private function createCachingVisitor() : NodeVisitorAbstract
184
    {
185
        return new class() extends NodeVisitorAbstract
186
        {
187
            /** @var array<string, Node\Stmt\ClassLike> */
188
            private $classNodes = [];
189
190
            /** @var array<string, Node\Stmt\Function_> */
191
            private $functionNodes = [];
192
193
            /** @var array<string, Node\Stmt\Const_|Node\Expr\FuncCall> */
194
            private $constantNodes = [];
195
196 1179
            public function enterNode(Node $node) : ?int
197
            {
198 1179
                if ($node instanceof Node\Stmt\ClassLike) {
199 181
                    $nodeName                    = (string) $node->namespacedName->toString();
200 181
                    $this->classNodes[$nodeName] = $node;
201
202 181
                    return NodeTraverser::DONT_TRAVERSE_CHILDREN;
203
                }
204
205 1147
                if ($node instanceof Node\Stmt\Function_) {
206
                    /** @psalm-suppress UndefinedPropertyFetch */
207 680
                    $nodeName                       = (string) $node->namespacedName->toString();
208 680
                    $this->functionNodes[$nodeName] = $node;
209
210 680
                    return NodeTraverser::DONT_TRAVERSE_CHILDREN;
211
                }
212
213 607
                if ($node instanceof Node\Stmt\Const_) {
214
                    foreach ($node->consts as $constNode) {
215
                        /** @psalm-suppress UndefinedPropertyFetch */
216
                        $constNodeName                       = (string) $constNode->namespacedName->toString();
217
                        $this->constantNodes[$constNodeName] = $node;
218
                    }
219
220
                    return NodeTraverser::DONT_TRAVERSE_CHILDREN;
221
                }
222
223 607
                if ($node instanceof Node\Expr\FuncCall) {
224
                    try {
225 605
                        ConstantNodeChecker::assertValidDefineFunctionCall($node);
226 101
                    } catch (InvalidConstantNode $e) {
227 101
                        return null;
228
                    }
229
230 605
                    $nameNode = $node->args[0]->value;
231 605
                    assert($nameNode instanceof Node\Scalar\String_);
232 605
                    $constantName = $nameNode->value;
233
234
                    // Some constants has different values on different systems, some are not actual in stubs
235 605
                    if (defined($constantName)) {
236
                        /** @psalm-var scalar|scalar[]|null $constantValue */
237 596
                        $constantValue        = constant($constantName);
238 596
                        $node->args[1]->value = BuilderHelpers::normalizeValue($constantValue);
239
                    }
240
241 605
                    $this->constantNodes[$constantName] = $node;
242
243 605
                    if (count($node->args) === 3
244 605
                        && $node->args[2]->value instanceof Node\Expr\ConstFetch
245 605
                        && $node->args[2]->value->name->toLowerString() === 'true'
246
                    ) {
247 108
                        $this->constantNodes[strtolower($constantName)] = $node;
248
                    }
249
250 605
                    return NodeTraverser::DONT_TRAVERSE_CHILDREN;
251
                }
252
253 607
                return null;
254
            }
255
256
            /**
257
             * @return array<string, Node\Stmt\ClassLike>
258
             */
259 1179
            public function getClassNodes() : array
260
            {
261 1179
                return $this->classNodes;
262
            }
263
264
            /**
265
             * @return array<string, Node\Stmt\Function_>
266
             */
267 1179
            public function getFunctionNodes() : array
268
            {
269 1179
                return $this->functionNodes;
270
            }
271
272
            /**
273
             * @return array<string, Node\Stmt\Const_|Node\Expr\FuncCall>
274
             */
275 1179
            public function getConstantNodes() : array
276
            {
277 1179
                return $this->constantNodes;
278
            }
279
280 1179
            public function clearNodes() : void
281
            {
282 1179
                $this->classNodes    = [];
283 1179
                $this->functionNodes = [];
284 1179
                $this->constantNodes = [];
285 1179
            }
286
        };
287
    }
288
289 1179
    private function getExtensionFromFilePath(string $filePath) : string
290
    {
291 1179
        return explode('/', $filePath)[0];
292
    }
293
294 1179
    private function getAbsoluteFilePath(string $filePath) : string
295
    {
296 1179
        return sprintf('%s/%s', $this->getStubsDirectory(), $filePath);
297
    }
298
299 1179
    private function getStubsDirectory() : string
300
    {
301 1179
        if ($this->stubsDirectory !== null) {
302 55
            return $this->stubsDirectory;
303
        }
304
305 1179
        foreach (self::SEARCH_DIRECTORIES as $directory) {
306 1179
            if (is_dir($directory)) {
307 1179
                return $this->stubsDirectory = $directory;
308
            }
309
        }
310
311
        throw CouldNotFindPhpStormStubs::create();
312
    }
313
}
314