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/ReflectionParameter.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 Exception;
9
use InvalidArgumentException;
10
use LogicException;
11
use OutOfBoundsException;
12
use phpDocumentor\Reflection\Type;
13
use PhpParser\Node;
14
use PhpParser\Node\NullableType;
15
use PhpParser\Node\Param as ParamNode;
16
use PhpParser\Node\Stmt\Namespace_;
17
use Roave\BetterReflection\NodeCompiler\CompileNodeToValue;
18
use Roave\BetterReflection\NodeCompiler\CompilerContext;
19
use Roave\BetterReflection\Reflection\Exception\Uncloneable;
20
use Roave\BetterReflection\Reflection\StringCast\ReflectionParameterStringCast;
21
use Roave\BetterReflection\Reflector\ClassReflector;
22
use Roave\BetterReflection\Reflector\Reflector;
23
use Roave\BetterReflection\TypesFinder\FindParameterType;
24
use Roave\BetterReflection\Util\CalculateReflectionColum;
25
use RuntimeException;
26
use function assert;
27
use function count;
28
use function get_class;
29
use function in_array;
30
use function is_array;
31
use function is_object;
32
use function is_string;
33
use function sprintf;
34
use function strtolower;
35
36
class ReflectionParameter
37
{
38
    /** @var ParamNode */
39
    private $node;
40
41
    /** @var Namespace_|null */
42
    private $declaringNamespace;
43
44
    /** @var ReflectionFunctionAbstract */
45
    private $function;
46
47
    /** @var int */
48
    private $parameterIndex;
49
50
    /**
51
     * @var bool|int|float|string|array<bool|int|float|string>|null
52
     * @psalm-var scalar|array<scalar>|null
53
     */
54
    private $defaultValue;
55
56
    /** @var bool */
57
    private $isDefaultValueConstant = false;
58
59
    /** @var string|null */
60
    private $defaultValueConstantName;
61
62
    /** @var Reflector */
63
    private $reflector;
64
65 57
    private function __construct()
66
    {
67 57
    }
68
69
    /**
70
     * Create a reflection of a parameter using a class name
71
     *
72
     * @throws OutOfBoundsException
73
     */
74 2
    public static function createFromClassNameAndMethod(
75
        string $className,
76
        string $methodName,
77
        string $parameterName
78
    ) : self {
79 2
        return ReflectionClass::createFromName($className)
80 2
            ->getMethod($methodName)
81 2
            ->getParameter($parameterName);
82
    }
83
84
    /**
85
     * Create a reflection of a parameter using an instance
86
     *
87
     * @param object $instance
88
     *
89
     * @throws OutOfBoundsException
90
     */
91 2
    public static function createFromClassInstanceAndMethod(
92
        $instance,
93
        string $methodName,
94
        string $parameterName
95
    ) : self {
96 2
        return ReflectionClass::createFromInstance($instance)
97 2
            ->getMethod($methodName)
98 2
            ->getParameter($parameterName);
99
    }
100
101
    /**
102
     * Create a reflection of a parameter using a closure
103
     */
104 2
    public static function createFromClosure(Closure $closure, string $parameterName) : ReflectionParameter
105
    {
106 2
        return ReflectionFunction::createFromClosure($closure)
107 2
            ->getParameter($parameterName);
108
    }
109
110
    /**
111
     * Create the parameter from the given spec. Possible $spec parameters are:
112
     *
113
     *  - [$instance, 'method']
114
     *  - ['Foo', 'bar']
115
     *  - ['foo']
116
     *  - [function () {}]
117
     *
118
     * @param object[]|string[]|string|Closure $spec
119
     *
120
     * @throws Exception
121
     * @throws InvalidArgumentException
122
     */
123 5
    public static function createFromSpec($spec, string $parameterName) : self
124
    {
125 5
        if (is_array($spec) && count($spec) === 2 && is_string($spec[1])) {
126 2
            if (is_object($spec[0])) {
127 1
                return self::createFromClassInstanceAndMethod($spec[0], $spec[1], $parameterName);
128
            }
129
130 1
            return self::createFromClassNameAndMethod($spec[0], $spec[1], $parameterName);
131
        }
132
133 3
        if (is_string($spec)) {
134 1
            return ReflectionFunction::createFromName($spec)->getParameter($parameterName);
135
        }
136
137 2
        if ($spec instanceof Closure) {
138 1
            return self::createFromClosure($spec, $parameterName);
139
        }
140
141 1
        throw new InvalidArgumentException('Could not create reflection from the spec given');
142
    }
143
144 1
    public function __toString() : string
145
    {
146 1
        return ReflectionParameterStringCast::toString($this);
147
    }
148
149
    /**
150
     * @internal
151
     *
152
     * @param ParamNode       $node               Node has to be processed by the PhpParser\NodeVisitor\NameResolver
153
     * @param Namespace_|null $declaringNamespace namespace of the declaring function/method
154
     */
155 57
    public static function createFromNode(
156
        Reflector $reflector,
157
        ParamNode $node,
158
        ?Namespace_ $declaringNamespace,
159
        ReflectionFunctionAbstract $function,
160
        int $parameterIndex
161
    ) : self {
162 57
        $param                     = new self();
163 57
        $param->reflector          = $reflector;
164 57
        $param->node               = $node;
165 57
        $param->declaringNamespace = $declaringNamespace;
166 57
        $param->function           = $function;
167 57
        $param->parameterIndex     = $parameterIndex;
168
169 57
        return $param;
170
    }
171
172 13
    private function parseDefaultValueNode() : void
173
    {
174 13
        if (! $this->isDefaultValueAvailable()) {
175 1
            throw new LogicException('This parameter does not have a default value available');
176
        }
177
178 12
        $defaultValueNode = $this->node->default;
179
180 12
        if ($defaultValueNode instanceof Node\Expr\ClassConstFetch) {
181 2
            assert($defaultValueNode->class instanceof Node\Name);
182 2
            $className = $defaultValueNode->class->toString();
0 ignored issues
show
The method toString does only exist in PhpParser\Node\Name, but not in PhpParser\Node\Expr.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
183
184 2
            if ($className === 'self' || $className === 'static') {
185 2
                assert($defaultValueNode->name instanceof Node\Identifier);
186 2
                $constantName = $defaultValueNode->name->name;
187 2
                $className    = $this->findParentClassDeclaringConstant($constantName);
188
            }
189
190 2
            $this->isDefaultValueConstant = true;
191 2
            assert($defaultValueNode->name instanceof Node\Identifier);
192 2
            $this->defaultValueConstantName = $className . '::' . $defaultValueNode->name->name;
193
        }
194
195 12
        if ($defaultValueNode instanceof Node\Expr\ConstFetch
196 12
            && ! in_array(strtolower($defaultValueNode->name->parts[0]), ['true', 'false', 'null'], true)) {
197 1
            $this->isDefaultValueConstant   = true;
198 1
            $this->defaultValueConstantName = $defaultValueNode->name->parts[0];
199 1
            $this->defaultValue             = null;
200
201 1
            return;
202
        }
203
204 12
        $this->defaultValue = (new CompileNodeToValue())->__invoke(
205 12
            $defaultValueNode,
206 12
            new CompilerContext($this->reflector, $this->getDeclaringClass())
207
        );
208 12
    }
209
210
    /**
211
     * @throws LogicException
212
     */
213 2
    private function findParentClassDeclaringConstant(string $constantName) : string
214
    {
215 2
        $method = $this->function;
216 2
        assert($method instanceof ReflectionMethod);
217 2
        $class = $method->getDeclaringClass();
218
219
        do {
220 2
            if ($class->hasConstant($constantName)) {
221 2
                return $class->getName();
222
            }
223
224
            $class = $class->getParentClass();
225
        } while ($class);
226
227
        // note: this code is theoretically unreachable, so don't expect any coverage on it
228
        throw new LogicException(sprintf('Failed to find parent class of constant "%s".', $constantName));
229
    }
230
231
    /**
232
     * Get the name of the parameter.
233
     */
234 57
    public function getName() : string
235
    {
236 57
        assert(is_string($this->node->var->name));
237
238 57
        return $this->node->var->name;
239
    }
240
241
    /**
242
     * Get the function (or method) that declared this parameter.
243
     */
244 1
    public function getDeclaringFunction() : ReflectionFunctionAbstract
245
    {
246 1
        return $this->function;
247
    }
248
249
    /**
250
     * Get the class from the method that this parameter belongs to, if it
251
     * exists.
252
     *
253
     * This will return null if the declaring function is not a method.
254
     */
255 16
    public function getDeclaringClass() : ?ReflectionClass
256
    {
257 16
        if ($this->function instanceof ReflectionMethod) {
258 15
            return $this->function->getDeclaringClass();
259
        }
260
261 1
        return null;
262
    }
263
264
    /**
265
     * Is the parameter optional?
266
     *
267
     * Note this is distinct from "isDefaultValueAvailable" because you can have
268
     * a default value, but the parameter not be optional. In the example, the
269
     * $foo parameter isOptional() == false, but isDefaultValueAvailable == true
270
     *
271
     * @example someMethod($foo = 'foo', $bar)
272
     */
273 3
    public function isOptional() : bool
274
    {
275 3
        return ((bool) $this->node->isOptional) || $this->isVariadic();
276
    }
277
278
    /**
279
     * Does the parameter have a default, regardless of whether it is optional.
280
     *
281
     * Note this is distinct from "isOptional" because you can have
282
     * a default value, but the parameter not be optional. In the example, the
283
     * $foo parameter isOptional() == false, but isDefaultValueAvailable == true
284
     *
285
     * @example someMethod($foo = 'foo', $bar)
286
     */
287 28
    public function isDefaultValueAvailable() : bool
288
    {
289 28
        return $this->node->default !== null;
290
    }
291
292
    /**
293
     * Get the default value of the parameter.
294
     *
295
     * @return bool|int|float|string|array|null
296
     *
297
     * @throws LogicException
298
     *
299
     * @psalm-return scalar|array<scalar>|null
300
     */
301 11
    public function getDefaultValue()
302
    {
303 11
        $this->parseDefaultValueNode();
304
305 10
        return $this->defaultValue;
306
    }
307
308
    /**
309
     * Does this method allow null for a parameter?
310
     */
311 19
    public function allowsNull() : bool
312
    {
313 19
        if (! $this->hasType()) {
314 1
            return true;
315
        }
316
317 19
        if ($this->node->type instanceof NullableType) {
318 3
            return true;
319
        }
320
321 16
        if (! $this->isDefaultValueAvailable()) {
322 15
            return false;
323
        }
324
325 2
        return $this->getDefaultValue() === null;
326
    }
327
328
    /**
329
     * Get the DocBlock type hints as an array of strings.
330
     *
331
     * @return string[]
332
     */
333 1
    public function getDocBlockTypeStrings() : array
334
    {
335 1
        $stringTypes = [];
336
337 1
        foreach ($this->getDocBlockTypes() as $type) {
338 1
            $stringTypes[] = (string) $type;
339
        }
340
341 1
        return $stringTypes;
342
    }
343
344
    /**
345
     * Get the types defined in the DocBlocks. This returns an array because
346
     * the parameter may have multiple (compound) types specified (for example
347
     * when you type hint pipe-separated "string|null", in which case this
348
     * would return an array of Type objects, one for string, one for null.
349
     *
350
     * @see getTypeHint()
351
     *
352
     * @return Type[]
353
     */
354 2
    public function getDocBlockTypes() : array
355
    {
356 2
        return (new FindParameterType())->__invoke($this->function, $this->declaringNamespace, $this->node);
357
    }
358
359
    /**
360
     * Find the position of the parameter, left to right, starting at zero.
361
     */
362 2
    public function getPosition() : int
363
    {
364 2
        return $this->parameterIndex;
365
    }
366
367
    /**
368
     * Get the ReflectionType instance representing the type declaration for
369
     * this parameter
370
     *
371
     * (note: this has nothing to do with DocBlocks).
372
     */
373 20
    public function getType() : ?ReflectionType
374
    {
375 20
        $type = $this->node->type;
376
377 20
        if ($type === null) {
378 2
            return null;
379
        }
380
381 18
        if ($type instanceof NullableType) {
382 3
            $type = $type->type;
383
        }
384
385 18
        return ReflectionType::createFromTypeAndReflector((string) $type, $this->allowsNull(), $this->reflector);
386
    }
387
388
    /**
389
     * Does this parameter have a type declaration?
390
     *
391
     * (note: this has nothing to do with DocBlocks).
392
     */
393 22
    public function hasType() : bool
394
    {
395 22
        return $this->node->type !== null;
396
    }
397
398
    /**
399
     * Set the parameter type declaration.
400
     */
401 1
    public function setType(string $newParameterType) : void
402
    {
403 1
        $this->node->type = new Node\Name($newParameterType);
404 1
    }
405
406
    /**
407
     * Remove the parameter type declaration completely.
408
     */
409 1
    public function removeType() : void
410
    {
411 1
        $this->node->type = null;
412 1
    }
413
414
    /**
415
     * Is this parameter an array?
416
     */
417 1
    public function isArray() : bool
418
    {
419 1
        return strtolower((string) $this->getType()) === 'array';
420
    }
421
422
    /**
423
     * Is this parameter a callable?
424
     */
425 1
    public function isCallable() : bool
426
    {
427 1
        return strtolower((string) $this->getType()) === 'callable';
428
    }
429
430
    /**
431
     * Is this parameter a variadic (denoted by ...$param).
432
     */
433 4
    public function isVariadic() : bool
434
    {
435 4
        return $this->node->variadic;
436
    }
437
438
    /**
439
     * Is this parameter passed by reference (denoted by &$param).
440
     */
441 2
    public function isPassedByReference() : bool
442
    {
443 2
        return $this->node->byRef;
444
    }
445
446 1
    public function canBePassedByValue() : bool
447
    {
448 1
        return ! $this->isPassedByReference();
449
    }
450
451 2
    public function isDefaultValueConstant() : bool
452
    {
453 2
        $this->parseDefaultValueNode();
454
455 2
        return $this->isDefaultValueConstant;
456
    }
457
458
    /**
459
     * @throws LogicException
460
     */
461 2
    public function getDefaultValueConstantName() : string
462
    {
463 2
        $this->parseDefaultValueNode();
464 2
        if (! $this->isDefaultValueConstant()) {
465 1
            throw new LogicException('This parameter is not a constant default value, so cannot have a constant name');
466
        }
467
468 2
        return $this->defaultValueConstantName;
469
    }
470
471
    /**
472
     * Gets a ReflectionClass for the type hint (returns null if not a class)
473
     *
474
     * @throws RuntimeException
475
     */
476 4
    public function getClass() : ?ReflectionClass
477
    {
478 4
        $className = $this->getClassName();
479
480 4
        if ($className === null) {
481 2
            return null;
482
        }
483
484 3
        if (! $this->reflector instanceof ClassReflector) {
485
            throw new RuntimeException(sprintf(
486
                'Unable to reflect class type because we were not given a "%s", but a "%s" instead',
487
                ClassReflector::class,
488
                get_class($this->reflector)
489
            ));
490
        }
491
492 3
        return $this->reflector->reflect($className);
493
    }
494
495 4
    private function getClassName() : ?string
496
    {
497 4
        if (! $this->hasType()) {
498 1
            return null;
499
        }
500
501 4
        $type = $this->getType();
502 4
        assert($type instanceof ReflectionType);
503 4
        $typeHint = (string) $type;
504
505 4
        if ($typeHint === 'self') {
506 1
            $declaringClass = $this->getDeclaringClass();
507 1
            assert($declaringClass instanceof ReflectionClass);
508
509 1
            return $declaringClass->getName();
510
        }
511
512 3
        if ($typeHint === 'parent') {
513 1
            $declaringClass = $this->getDeclaringClass();
514 1
            assert($declaringClass instanceof ReflectionClass);
515 1
            $parentClass = $declaringClass->getParentClass();
516 1
            assert($parentClass instanceof ReflectionClass);
517
518 1
            return $parentClass->getName();
519
        }
520
521 2
        if ($type->isBuiltin()) {
522 2
            return null;
523
        }
524
525 1
        return $typeHint;
526
    }
527
528
    /**
529
     * {@inheritdoc}
530
     *
531
     * @throws Uncloneable
532
     */
533 1
    public function __clone()
534
    {
535 1
        throw Uncloneable::fromClass(self::class);
536
    }
537
538 4
    public function getStartColumn() : int
539
    {
540 4
        return CalculateReflectionColum::getStartColumn($this->function->getLocatedSource()->getSource(), $this->node);
541
    }
542
543 4
    public function getEndColumn() : int
544
    {
545 4
        return CalculateReflectionColum::getEndColumn($this->function->getLocatedSource()->getSource(), $this->node);
546
    }
547
548 1
    public function getAst() : ParamNode
549
    {
550 1
        return $this->node;
551
    }
552
}
553