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)

Check that return values of null are not assigned.

Bug Minor

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 1
    protected function getNode() : Node\FunctionLike
92
    {
93 1
        assert($this->node !== null);
94
95
        return $this->node;
96
    }
97
98
    /**
99
     * We must determine if params are optional or not ahead of time, but we
100 70
     * must do it in reverse...
101
     */
102 70
    private function setNodeOptionalFlag() : void
103 70
    {
104 70
        $overallOptionalFlag = true;
105 10
        $lastParamIndex      = count($this->getNode()->params) - 1;
106
        for ($i = $lastParamIndex; $i >= 0; $i--) {
107
            $hasDefault = ($this->getNode()->params[$i]->default !== null);
108
109
            // When we find the first parameter that does not have a default,
110 10
            // flip the flag as all params for this are no longer optional
111 10
            // EVEN if they have a default value
112
            if (! $hasDefault) {
113
                $overallOptionalFlag = false;
114 10
            }
115
116 70
            $this->getNode()->params[$i]->isOptional = $overallOptionalFlag;
117
        }
118
    }
119
120
    /**
121
     * Get the "full" name of the function (e.g. for A\B\foo, this will return
122 68
     * "A\B\foo").
123
     */
124 68
    public function getName() : string
125 65
    {
126
        if (! $this->inNamespace()) {
127
            return $this->getShortName();
128 3
        }
129
130
        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 68
     * "foo").
136
     */
137 68
    public function getShortName() : string
138 1
    {
139
        $initializedNode = $this->getNode();
140
        if ($initializedNode instanceof Node\Expr\Closure) {
141 67
            return self::CLOSURE_NAME;
142
        }
143
144
        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 4
     *
151
     * @psalm-suppress PossiblyNullPropertyFetch
152 4
     */
153 1
    public function getNamespaceName() : string
154
    {
155
        if (! $this->inNamespace()) {
156 3
            return '';
157
        }
158
159
        return implode('\\', $this->declaringNamespace->name->parts);
160
    }
161
162
    /**
163 68
     * 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 68
     */
166 68
    public function inNamespace() : bool
167
    {
168
        return $this->declaringNamespace !== null
169
            && $this->declaringNamespace->name !== null;
170
    }
171
172 1
    /**
173
     * Get the number of parameters for this class.
174 1
     */
175
    public function getNumberOfParameters() : int
176
    {
177
        return count($this->getParameters());
178
    }
179
180 1
    /**
181
     * Get the number of required parameters for this method.
182 1
     */
183 1
    public function getNumberOfRequiredParameters() : int
184
    {
185 1
        return count(array_filter(
186 1
            $this->getParameters(),
187
            static function (ReflectionParameter $p) : bool {
188
                return ! $p->isOptional();
189
            }
190
        ));
191
    }
192
193
    /**
194
     * Get an array list of the parameters for this method signature, as an
195
     * array of ReflectionParameter instances.
196 6
     *
197
     * @return ReflectionParameter[]
198 6
     *
199
     * @psalm-return list<ReflectionParameter>
200 6
     */
201 6
    public function getParameters() : array
202 6
    {
203
        $parameters = [];
204 6
205
        /** @var list<Node\Param> $nodeParams */
206
        $nodeParams = $this->getNode()->params;
207
        foreach ($nodeParams as $paramIndex => $paramNode) {
208
            $parameters[] = ReflectionParameter::createFromNode(
209
                $this->reflector,
210 6
                $paramNode,
211
                $this->declaringNamespace,
212
                $this,
213
                $paramIndex
214
            );
215
        }
216
217 2
        return $parameters;
218
    }
219 2
220 2
    /**
221 1
     * Get a single parameter by name. Returns null if parameter not found for
222
     * the function.
223
     */
224
    public function getParameter(string $parameterName) : ?ReflectionParameter
225 1
    {
226
        foreach ($this->getParameters() as $parameter) {
227
            if ($parameter->getName() === $parameterName) {
228 4
                return $parameter;
229
            }
230 4
        }
231
232
        return null;
233 1
    }
234
235 1
    public function getDocComment() : string
236 1
    {
237
        return GetFirstDocComment::forNode($this->getNode());
238 2
    }
239
240 2
    public function setDocCommentFromString(string $string) : void
241
    {
242
        $this->getAst()->setDocComment(new Doc($string));
243 1
    }
244
245 1
    public function getFileName() : ?string
246
    {
247
        return $this->locatedSource->getFileName();
248
    }
249
250
    public function getLocatedSource() : LocatedSource
251 2
    {
252
        return $this->locatedSource;
253 2
    }
254
255
    /**
256
     * Is this function a closure?
257
     */
258
    public function isClosure() : bool
259
    {
260
        return $this->node instanceof Node\Expr\Closure;
261
    }
262
263
    /**
264
     * Is this function deprecated?
265 1
     *
266
     * Note - we cannot reflect on internal functions (as there is no PHP source
267 1
     * code we can access. This means, at present, we can only EVER return false
268
     * from this function.
269
     *
270 1
     * @see https://github.com/Roave/BetterReflection/issues/38
271
     */
272 1
    public function isDeprecated() : bool
273
    {
274
        return false;
275
    }
276
277
    public function isInternal() : bool
278
    {
279 1
        return $this->locatedSource->isInternal();
280
    }
281 1
282
    /**
283
     * Is this a user-defined function (will always return the opposite of
284
     * whatever isInternal returns).
285
     */
286
    public function isUserDefined() : bool
287
    {
288
        return ! $this->isInternal();
289
    }
290
291
    public function getExtensionName() : ?string
292 3
    {
293
        return $this->locatedSource->getExtensionName();
294 3
    }
295
296 3
    /**
297 3
     * Check if the function has a variadic parameter.
298 2
     */
299
    public function isVariadic() : bool
300
    {
301
        $parameters = $this->getParameters();
302 1
303
        foreach ($parameters as $parameter) {
304
            if ($parameter->isVariadic()) {
305
                return true;
306
            }
307
        }
308
309 16
        return false;
310
    }
311 16
312 15
    /**
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 16
     */
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
            if ($nodeProperty instanceof Node && $this->nodeIsOrContainsYield($nodeProperty)) {
326 16
                return true;
327 16
            }
328 15
329
            if (! is_array($nodeProperty)) {
330
                continue;
331
            }
332
333 16
            foreach ($nodeProperty as $nodePropertyArrayItem) {
334
                if ($nodePropertyArrayItem instanceof Node && $this->nodeIsOrContainsYield($nodePropertyArrayItem)) {
335
                    return true;
336
                }
337
            }
338
        }
339
340 17
        return false;
341
    }
342 17
343 1
    /**
344
     * Check if this function can be used as a generator (i.e. contains the
345
     * "yield" keyword).
346 16
     */
347
    public function isGenerator() : bool
348
    {
349
        if ($this->node === null) {
350
            return false;
351
        }
352 3
353
        return $this->nodeIsOrContainsYield($this->node);
354 3
    }
355
356
    /**
357
     * Get the line number that this function starts on.
358
     */
359
    public function getStartLine() : int
360 3
    {
361
        return $this->getNode()->getStartLine();
362 3
    }
363
364
    /**
365 3
     * Get the line number that this function ends on.
366
     */
367 3
    public function getEndLine() : int
368
    {
369
        return $this->getNode()->getEndLine();
370 3
    }
371
372 3
    public function getStartColumn() : int
373
    {
374
        return CalculateReflectionColum::getStartColumn($this->locatedSource->getSource(), $this->getNode());
375
    }
376
377
    public function getEndColumn() : int
378 2
    {
379
        return CalculateReflectionColum::getEndColumn($this->locatedSource->getSource(), $this->getNode());
380 2
    }
381
382
    /**
383
     * Is this function declared as a reference.
384
     */
385
    public function returnsReference() : bool
386
    {
387
        return $this->getNode()->byRef;
388
    }
389
390
    /**
391 1
     * 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 1
     * 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
    public function getDocBlockReturnTypes() : array
399 13
    {
400
        return (new FindReturnType())->__invoke($this, $this->declaringNamespace);
401 13
    }
402
403 13
    /**
404 3
     * Get the return type declaration (only for PHP 7+ code)
405
     */
406
    public function getReturnType() : ?ReflectionType
407 10
    {
408 3
        $returnType = $this->getNode()->getReturnType();
409
410
        if ($returnType === null) {
411 7
            return null;
412
        }
413
414
        if ($returnType instanceof NullableType) {
415
            return ReflectionType::createFromTypeAndReflector((string) $returnType->type, true, $this->reflector);
416
        }
417 2
418
        return ReflectionType::createFromTypeAndReflector((string) $returnType, false, $this->reflector);
419 2
    }
420
421
    /**
422
     * Do we have a return type declaration (only for PHP 7+ code)
423
     */
424
    public function hasReturnType() : bool
425 1
    {
426
        return $this->getReturnType() !== null;
427 1
    }
428 1
429
    /**
430
     * Set the return type declaration.
431
     */
432
    public function setReturnType(string $newReturnType) : void
433 1
    {
434
        $this->getNode()->returnType = new Node\Name($newReturnType);
435 1
    }
436 1
437
    /**
438
     * Remove the return type declaration completely.
439
     */
440
    public function removeReturnType() : void
441 1
    {
442
        $this->getNode()->returnType = null;
443 1
    }
444
445
    /**
446
     * @throws Uncloneable
447
     */
448
    public function __clone()
449
    {
450
        throw Uncloneable::fromClass(self::class);
451 5
    }
452
453 5
    /**
454
     * Retrieves the body of this function as AST nodes
455
     *
456
     * @return Node[]
457
     */
458
    public function getBodyAst() : array
459
    {
460
        return $this->getNode()->stmts ?: [];
461
    }
462
463
    /**
464
     * Retrieves the body of this function as code.
465
     *
466 4
     * If a PrettyPrinter is provided as a parameter, it will be used, otherwise
467
     * a default will be used.
468 4
     *
469 4
     * 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 4
     */
473
    public function getBodyCode(?PrettyPrinterAbstract $printer = null) : string
474
    {
475
        if ($printer === null) {
476
            $printer = new StandardPrettyPrinter();
477
        }
478
479
        return $printer->prettyPrint($this->getBodyAst());
480 6
    }
481
482 6
    /**
483
     * Fetch the AST for this method or function.
484
     *
485
     * @return Node\Stmt\ClassMethod|Node\Stmt\Function_|Node\FunctionLike
486
     */
487
    public function getAst() : Node\FunctionLike
488
    {
489
        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 1
     *
496
     * @throws ParseToAstFailure
497 1
     * @throws InvalidIdentifierName
498 1
     *
499 1
     * @example
500
     * $reflectionFunction->setBodyFromClosure(function () { return true; });
501 1
     */
502
    public function setBodyFromClosure(Closure $newBody) : void
503 1
    {
504
        $closureReflection = (new ClosureSourceLocator($newBody, $this->loadStaticParser()))->locateIdentifier(
0 ignored issues
show
Are you sure the assignment to $closureReflection is correct as (new \Roave\BetterReflec...:IDENTIFIER_FUNCTION))) (which targets Roave\BetterReflection\S...tor::locateIdentifier()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
505 1
            $this->reflector,
506 1
            new Identifier(self::CLOSURE_NAME, new IdentifierType(IdentifierType::IDENTIFIER_FUNCTION))
507
        );
508
        assert($closureReflection instanceof self);
509
510
        $functionNode = $closureReflection->getNode();
511
512
        $this->getNode()->stmts = $functionNode->getStmts();
513
    }
514
515 1
    /**
516
     * Override the method or function's body of statements with an entirely new
517 1
     * body of statements within the reflection.
518 1
     *
519
     * @example
520
     * $reflectionFunction->setBodyFromString('return true;');
521
     */
522
    public function setBodyFromString(string $newBody) : void
523
    {
524
        $this->getNode()->stmts = $this->loadStaticParser()->parse('<?php ' . $newBody);
525
    }
526
527
    /**
528
     * Override the method or function's body of statements with an entirely new
529
     * body of statements within the reflection.
530 2
     *
531
     * @param Node[] $nodes
532
     *
533
     * @example
534
     * // $ast should be an array of Nodes
535 1
     * $reflectionFunction->setBodyFromAst($ast);
536 2
     */
537 2
    public function setBodyFromAst(array $nodes) : void
538 1
    {
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
            return $node;
543 1
        };
544
        $this->getNode()->stmts = $validator(...$nodes);
545 1
    }
546 1
547
    /**
548
     * Add a new parameter to the method/function.
549
     */
550
    public function addParameter(string $parameterName) : void
551 1
    {
552
        $this->getNode()->params[] = new ParamNode(new Node\Expr\Variable($parameterName));
553 1
    }
554
555 1
    /**
556 1
     * Remove a parameter from the method/function.
557
     */
558
    public function removeParameter(string $parameterName) : void
559
    {
560 1
        $lowerName = strtolower($parameterName);
561 1
562
        foreach ($this->getNode()->params as $key => $paramNode) {
563
            if ($paramNode->var instanceof Node\Expr\Error) {
564 1
                throw new LogicException('PhpParser left an "Error" node in the parameters AST, this should NOT happen');
565
            }
566 1
567
            if (! is_string($paramNode->var->name) || strtolower($paramNode->var->name) !== $lowerName) {
568
                continue;
569
            }
570
571
            unset($this->getNode()->params[$key]);
572
        }
573
    }
574
575
    /**
576 2
     * Fetch an array of all return statements found within this function.
577
     *
578 2
     * 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 2
     *
581 2
     * @return Node\Stmt\Return_[]
582
     */
583 2
    public function getReturnStatementsAst() : array
584
    {
585 2
        $visitor = new ReturnNodeVisitor();
586
587
        $traverser = new NodeTraverser();
588 2
        $traverser->addVisitor($visitor);
589
590 2
        $traverser->traverse($this->getNode()->getStmts());
591
592
        return $visitor->getReturnNodes();
593
    }
594
595
    private function loadStaticParser() : Parser
596
    {
597
        return self::$parser ?? self::$parser = (new BetterReflection())->phpParser();
598
    }
599
}
600