Issues (135)

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 (2 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\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 */
53
    private $node;
54
55
    /** @var Reflector */
56
    private $reflector;
57
58
    /** @var Parser */
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;
0 ignored issues
show
Documentation Bug introduced by Jaroslav Hanslík
It seems like $node of type object<PhpParser\Node\FunctionLike> is incompatible with the declared type object<PhpParser\Node\St...rser\Node\Expr\Closure> of property $node.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

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