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.

src/Reflection/ReflectionFunctionAbstract.php (1 issue)

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\Reflection;
6
7
use Closure;
8
use LogicException;
9
use phpDocumentor\Reflection\Type;
10
use PhpParser\Comment\Doc;
11
use PhpParser\Node;
12
use PhpParser\Node\Expr\Yield_ as YieldNode;
13
use PhpParser\Node\NullableType;
14
use PhpParser\Node\Param as ParamNode;
15
use PhpParser\Node\Stmt\Namespace_ as NamespaceNode;
16
use PhpParser\NodeTraverser;
17
use PhpParser\Parser;
18
use PhpParser\PrettyPrinter\Standard as StandardPrettyPrinter;
19
use PhpParser\PrettyPrinterAbstract;
20
use Roave\BetterReflection\BetterReflection;
21
use Roave\BetterReflection\Identifier\Exception\InvalidIdentifierName;
22
use Roave\BetterReflection\Identifier\Identifier;
23
use Roave\BetterReflection\Identifier\IdentifierType;
24
use Roave\BetterReflection\Reflection\Exception\InvalidAbstractFunctionNodeType;
25
use Roave\BetterReflection\Reflection\Exception\Uncloneable;
26
use Roave\BetterReflection\Reflector\Reflector;
27
use Roave\BetterReflection\SourceLocator\Ast\Exception\ParseToAstFailure;
28
use Roave\BetterReflection\SourceLocator\Located\LocatedSource;
29
use Roave\BetterReflection\SourceLocator\Type\ClosureSourceLocator;
30
use Roave\BetterReflection\TypesFinder\FindReturnType;
31
use Roave\BetterReflection\Util\CalculateReflectionColum;
32
use Roave\BetterReflection\Util\GetFirstDocComment;
33
use Roave\BetterReflection\Util\Visitor\ReturnNodeVisitor;
34
use function array_filter;
35
use function assert;
36
use function count;
37
use function implode;
38
use function is_array;
39
use function is_string;
40
use function strtolower;
41
42
abstract class ReflectionFunctionAbstract
43
{
44
    public const CLOSURE_NAME = '{closure}';
45
46
    /** @var NamespaceNode|null */
47
    private $declaringNamespace;
48
49
    /** @var LocatedSource */
50
    private $locatedSource;
51
52
    /** @var Node\Stmt\ClassMethod|Node\Stmt\Function_|Node\Expr\Closure|null */
53
    private $node;
54
55
    /** @var Reflector */
56
    private $reflector;
57
58
    /** @var Parser|null */
59
    private static $parser;
60
61 70
    protected function __construct()
62
    {
63 70
    }
64
65
    /**
66
     * Populate the common elements of the function abstract.
67
     *
68
     * @param Node\Stmt\ClassMethod|Node\Stmt\Function_|Node\Expr\Closure $node Node has to be processed by the PhpParser\NodeVisitor\NameResolver
69
     *
70
     * @throws InvalidAbstractFunctionNodeType
71
     */
72 70
    protected function populateFunctionAbstract(
73
        Reflector $reflector,
74
        Node\FunctionLike $node,
75
        LocatedSource $locatedSource,
76
        ?NamespaceNode $declaringNamespace = null
77
    ) : void {
78 70
        $this->reflector          = $reflector;
79 70
        $this->node               = $node;
80 70
        $this->locatedSource      = $locatedSource;
81 70
        $this->declaringNamespace = $declaringNamespace;
82
83 70
        $this->setNodeOptionalFlag();
84 70
    }
85
86
    /**
87
     * Get the AST node from which this function was created
88
     *
89
     * @return Node\Expr\Closure|Node\Stmt\ClassMethod|Node\Stmt\Function_
90
     */
91 70
    protected function getNode() : Node\FunctionLike
92
    {
93 70
        assert($this->node !== null);
94
95 70
        return $this->node;
96
    }
97
98
    /**
99
     * We must determine if params are optional or not ahead of time, but we
100
     * must do it in reverse...
101
     */
102 70
    private function setNodeOptionalFlag() : void
103
    {
104 70
        $overallOptionalFlag = true;
105 70
        $lastParamIndex      = count($this->getNode()->params) - 1;
106 70
        for ($i = $lastParamIndex; $i >= 0; $i--) {
107 10
            $hasDefault = ($this->getNode()->params[$i]->default !== null);
108
109
            // When we find the first parameter that does not have a default,
110
            // flip the flag as all params for this are no longer optional
111
            // EVEN if they have a default value
112 10
            if (! $hasDefault) {
113 10
                $overallOptionalFlag = false;
114
            }
115
116 10
            $this->getNode()->params[$i]->isOptional = $overallOptionalFlag;
117
        }
118 70
    }
119
120
    /**
121
     * Get the "full" name of the function (e.g. for A\B\foo, this will return
122
     * "A\B\foo").
123
     */
124 68
    public function getName() : string
125
    {
126 68
        if (! $this->inNamespace()) {
127 65
            return $this->getShortName();
128
        }
129
130 3
        return $this->getNamespaceName() . '\\' . $this->getShortName();
131
    }
132
133
    /**
134
     * Get the "short" name of the function (e.g. for A\B\foo, this will return
135
     * "foo").
136
     */
137 68
    public function getShortName() : string
138
    {
139 68
        $initializedNode = $this->getNode();
140 68
        if ($initializedNode instanceof Node\Expr\Closure) {
141 1
            return self::CLOSURE_NAME;
142
        }
143
144 67
        return $initializedNode->name->name;
145
    }
146
147
    /**
148
     * Get the "namespace" name of the function (e.g. for A\B\foo, this will
149
     * return "A\B").
150
     *
151
     * @psalm-suppress PossiblyNullPropertyFetch
152
     */
153 4
    public function getNamespaceName() : string
154
    {
155 4
        if (! $this->inNamespace()) {
156 1
            return '';
157
        }
158
159 3
        return implode('\\', $this->declaringNamespace->name->parts);
160
    }
161
162
    /**
163
     * Decide if this function is part of a namespace. Returns false if the class
164
     * is in the global namespace or does not have a specified namespace.
165
     */
166 68
    public function inNamespace() : bool
167
    {
168 68
        return $this->declaringNamespace !== null
169 68
            && $this->declaringNamespace->name !== null;
170
    }
171
172
    /**
173
     * Get the number of parameters for this class.
174
     */
175 1
    public function getNumberOfParameters() : int
176
    {
177 1
        return count($this->getParameters());
178
    }
179
180
    /**
181
     * Get the number of required parameters for this method.
182
     */
183 1
    public function getNumberOfRequiredParameters() : int
184
    {
185 1
        return count(array_filter(
186 1
            $this->getParameters(),
187
            static function (ReflectionParameter $p) : bool {
188 1
                return ! $p->isOptional();
189 1
            }
190
        ));
191
    }
192
193
    /**
194
     * Get an array list of the parameters for this method signature, as an
195
     * array of ReflectionParameter instances.
196
     *
197
     * @return ReflectionParameter[]
198
     *
199
     * @psalm-return list<ReflectionParameter>
200
     */
201 6
    public function getParameters() : array
202
    {
203 6
        $parameters = [];
204
205
        /** @var list<Node\Param> $nodeParams */
0 ignored issues
show
The doc-type list<Node\Param> could not be parsed: Expected "|" or "end of type", but got "<" at position 4. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
206 6
        $nodeParams = $this->getNode()->params;
207 6
        foreach ($nodeParams as $paramIndex => $paramNode) {
208 6
            $parameters[] = ReflectionParameter::createFromNode(
209 6
                $this->reflector,
210
                $paramNode,
211 6
                $this->declaringNamespace,
212
                $this,
213
                $paramIndex
214
            );
215
        }
216
217 6
        return $parameters;
218
    }
219
220
    /**
221
     * Get a single parameter by name. Returns null if parameter not found for
222
     * the function.
223
     */
224 2
    public function getParameter(string $parameterName) : ?ReflectionParameter
225
    {
226 2
        foreach ($this->getParameters() as $parameter) {
227 2
            if ($parameter->getName() === $parameterName) {
228 1
                return $parameter;
229
            }
230
        }
231
232 1
        return null;
233
    }
234
235 4
    public function getDocComment() : string
236
    {
237 4
        return GetFirstDocComment::forNode($this->getNode());
238
    }
239
240 1
    public function setDocCommentFromString(string $string) : void
241
    {
242 1
        $this->getAst()->setDocComment(new Doc($string));
243 1
    }
244
245 2
    public function getFileName() : ?string
246
    {
247 2
        return $this->locatedSource->getFileName();
248
    }
249
250 1
    public function getLocatedSource() : LocatedSource
251
    {
252 1
        return $this->locatedSource;
253
    }
254
255
    /**
256
     * Is this function a closure?
257
     */
258 2
    public function isClosure() : bool
259
    {
260 2
        return $this->node instanceof Node\Expr\Closure;
261
    }
262
263
    /**
264
     * Is this function deprecated?
265
     *
266
     * Note - we cannot reflect on internal functions (as there is no PHP source
267
     * code we can access. This means, at present, we can only EVER return false
268
     * from this function.
269
     *
270
     * @see https://github.com/Roave/BetterReflection/issues/38
271
     */
272 1
    public function isDeprecated() : bool
273
    {
274 1
        return false;
275
    }
276
277 1
    public function isInternal() : bool
278
    {
279 1
        return $this->locatedSource->isInternal();
280
    }
281
282
    /**
283
     * Is this a user-defined function (will always return the opposite of
284
     * whatever isInternal returns).
285
     */
286 1
    public function isUserDefined() : bool
287
    {
288 1
        return ! $this->isInternal();
289
    }
290
291
    public function getExtensionName() : ?string
292
    {
293
        return $this->locatedSource->getExtensionName();
294
    }
295
296
    /**
297
     * Check if the function has a variadic parameter.
298
     */
299 3
    public function isVariadic() : bool
300
    {
301 3
        $parameters = $this->getParameters();
302
303 3
        foreach ($parameters as $parameter) {
304 3
            if ($parameter->isVariadic()) {
305 2
                return true;
306
            }
307
        }
308
309 1
        return false;
310
    }
311
312
    /**
313
     * Recursively search an array of statements (PhpParser nodes) to find if a
314
     * yield expression exists anywhere (thus indicating this is a generator).
315
     */
316 16
    private function nodeIsOrContainsYield(Node $node) : bool
317
    {
318 16
        if ($node instanceof YieldNode) {
319 15
            return true;
320
        }
321
322 16
        foreach ($node->getSubNodeNames() as $nodeName) {
323 16
            $nodeProperty = $node->$nodeName;
324
325 16
            if ($nodeProperty instanceof Node && $this->nodeIsOrContainsYield($nodeProperty)) {
326 15
                return true;
327
            }
328
329 16
            if (! is_array($nodeProperty)) {
330 16
                continue;
331
            }
332
333 16
            foreach ($nodeProperty as $nodePropertyArrayItem) {
334 16
                if ($nodePropertyArrayItem instanceof Node && $this->nodeIsOrContainsYield($nodePropertyArrayItem)) {
335 15
                    return true;
336
                }
337
            }
338
        }
339
340 16
        return false;
341
    }
342
343
    /**
344
     * Check if this function can be used as a generator (i.e. contains the
345
     * "yield" keyword).
346
     */
347 17
    public function isGenerator() : bool
348
    {
349 17
        if ($this->node === null) {
350 1
            return false;
351
        }
352
353 16
        return $this->nodeIsOrContainsYield($this->node);
354
    }
355
356
    /**
357
     * Get the line number that this function starts on.
358
     */
359 3
    public function getStartLine() : int
360
    {
361 3
        return $this->getNode()->getStartLine();
362
    }
363
364
    /**
365
     * Get the line number that this function ends on.
366
     */
367 3
    public function getEndLine() : int
368
    {
369 3
        return $this->getNode()->getEndLine();
370
    }
371
372 3
    public function getStartColumn() : int
373
    {
374 3
        return CalculateReflectionColum::getStartColumn($this->locatedSource->getSource(), $this->getNode());
375
    }
376
377 3
    public function getEndColumn() : int
378
    {
379 3
        return CalculateReflectionColum::getEndColumn($this->locatedSource->getSource(), $this->getNode());
380
    }
381
382
    /**
383
     * Is this function declared as a reference.
384
     */
385 2
    public function returnsReference() : bool
386
    {
387 2
        return $this->getNode()->byRef;
388
    }
389
390
    /**
391
     * Get the return types defined in the DocBlocks. This returns an array because
392
     * the parameter may have multiple (compound) types specified (for example
393
     * when you type hint pipe-separated "string|null", in which case this
394
     * would return an array of Type objects, one for string, one for null.
395
     *
396
     * @return Type[]
397
     */
398 1
    public function getDocBlockReturnTypes() : array
399
    {
400 1
        return (new FindReturnType())->__invoke($this, $this->declaringNamespace);
401
    }
402
403
    /**
404
     * Get the return type declaration (only for PHP 7+ code)
405
     */
406 13
    public function getReturnType() : ?ReflectionType
407
    {
408 13
        $returnType = $this->getNode()->getReturnType();
409
410 13
        if ($returnType === null) {
411 3
            return null;
412
        }
413
414 10
        if ($returnType instanceof NullableType) {
415 3
            return ReflectionType::createFromTypeAndReflector((string) $returnType->type, true, $this->reflector);
416
        }
417
418 7
        return ReflectionType::createFromTypeAndReflector((string) $returnType, false, $this->reflector);
419
    }
420
421
    /**
422
     * Do we have a return type declaration (only for PHP 7+ code)
423
     */
424 2
    public function hasReturnType() : bool
425
    {
426 2
        return $this->getReturnType() !== null;
427
    }
428
429
    /**
430
     * Set the return type declaration.
431
     */
432 1
    public function setReturnType(string $newReturnType) : void
433
    {
434 1
        $this->getNode()->returnType = new Node\Name($newReturnType);
435 1
    }
436
437
    /**
438
     * Remove the return type declaration completely.
439
     */
440 1
    public function removeReturnType() : void
441
    {
442 1
        $this->getNode()->returnType = null;
443 1
    }
444
445
    /**
446
     * @throws Uncloneable
447
     */
448 1
    public function __clone()
449
    {
450 1
        throw Uncloneable::fromClass(self::class);
451
    }
452
453
    /**
454
     * Retrieves the body of this function as AST nodes
455
     *
456
     * @return Node[]
457
     */
458 5
    public function getBodyAst() : array
459
    {
460 5
        return $this->getNode()->stmts ?: [];
461
    }
462
463
    /**
464
     * Retrieves the body of this function as code.
465
     *
466
     * If a PrettyPrinter is provided as a parameter, it will be used, otherwise
467
     * a default will be used.
468
     *
469
     * Note that the formatting of the code may not be the same as the original
470
     * function. If specific formatting is required, you should provide your
471
     * own implementation of a PrettyPrinter to unparse the AST.
472
     */
473 4
    public function getBodyCode(?PrettyPrinterAbstract $printer = null) : string
474
    {
475 4
        if ($printer === null) {
476 4
            $printer = new StandardPrettyPrinter();
477
        }
478
479 4
        return $printer->prettyPrint($this->getBodyAst());
480
    }
481
482
    /**
483
     * Fetch the AST for this method or function.
484
     *
485
     * @return Node\Stmt\ClassMethod|Node\Stmt\Function_|Node\FunctionLike
486
     */
487 6
    public function getAst() : Node\FunctionLike
488
    {
489 6
        return $this->getNode();
490
    }
491
492
    /**
493
     * Override the method or function's body of statements with an entirely new
494
     * body of statements within the reflection.
495
     *
496
     * @throws ParseToAstFailure
497
     * @throws InvalidIdentifierName
498
     *
499
     * @example
500
     * $reflectionFunction->setBodyFromClosure(function () { return true; });
501
     */
502 1
    public function setBodyFromClosure(Closure $newBody) : void
503
    {
504 1
        $closureReflection = (new ClosureSourceLocator($newBody, $this->loadStaticParser()))->locateIdentifier(
505 1
            $this->reflector,
506 1
            new Identifier(self::CLOSURE_NAME, new IdentifierType(IdentifierType::IDENTIFIER_FUNCTION))
507
        );
508 1
        assert($closureReflection instanceof self);
509
510 1
        $functionNode = $closureReflection->getNode();
511
512 1
        $this->getNode()->stmts = $functionNode->getStmts();
513 1
    }
514
515
    /**
516
     * Override the method or function's body of statements with an entirely new
517
     * body of statements within the reflection.
518
     *
519
     * @example
520
     * $reflectionFunction->setBodyFromString('return true;');
521
     */
522 1
    public function setBodyFromString(string $newBody) : void
523
    {
524 1
        $this->getNode()->stmts = $this->loadStaticParser()->parse('<?php ' . $newBody);
525 1
    }
526
527
    /**
528
     * Override the method or function's body of statements with an entirely new
529
     * body of statements within the reflection.
530
     *
531
     * @param Node[] $nodes
532
     *
533
     * @example
534
     * // $ast should be an array of Nodes
535
     * $reflectionFunction->setBodyFromAst($ast);
536
     */
537 2
    public function setBodyFromAst(array $nodes) : void
538
    {
539
        // This slightly confusing code simply type-checks the $sourceLocators
540
        // array by unpacking them and splatting them in the closure.
541
        $validator              = static function (Node ...$node) : array {
542 1
            return $node;
543 2
        };
544 2
        $this->getNode()->stmts = $validator(...$nodes);
545 1
    }
546
547
    /**
548
     * Add a new parameter to the method/function.
549
     */
550 1
    public function addParameter(string $parameterName) : void
551
    {
552 1
        $this->getNode()->params[] = new ParamNode(new Node\Expr\Variable($parameterName));
553 1
    }
554
555
    /**
556
     * Remove a parameter from the method/function.
557
     */
558 1
    public function removeParameter(string $parameterName) : void
559
    {
560 1
        $lowerName = strtolower($parameterName);
561
562 1
        foreach ($this->getNode()->params as $key => $paramNode) {
563 1
            if ($paramNode->var instanceof Node\Expr\Error) {
564
                throw new LogicException('PhpParser left an "Error" node in the parameters AST, this should NOT happen');
565
            }
566
567 1
            if (! is_string($paramNode->var->name) || strtolower($paramNode->var->name) !== $lowerName) {
568 1
                continue;
569
            }
570
571 1
            unset($this->getNode()->params[$key]);
572
        }
573 1
    }
574
575
    /**
576
     * Fetch an array of all return statements found within this function.
577
     *
578
     * Note that return statements within smaller scopes contained (e.g. anonymous classes, closures) are not returned
579
     * here as they are not within the immediate scope.
580
     *
581
     * @return Node\Stmt\Return_[]
582
     */
583 2
    public function getReturnStatementsAst() : array
584
    {
585 2
        $visitor = new ReturnNodeVisitor();
586
587 2
        $traverser = new NodeTraverser();
588 2
        $traverser->addVisitor($visitor);
589
590 2
        $traverser->traverse($this->getNode()->getStmts());
591
592 2
        return $visitor->getReturnNodes();
593
    }
594
595 2
    private function loadStaticParser() : Parser
596
    {
597 2
        return self::$parser ?? self::$parser = (new BetterReflection())->phpParser();
598
    }
599
}
600