1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace PHPSA\Analyzer\Pass\Expression\FunctionCall; |
4
|
|
|
|
5
|
|
|
use PHPSA\Context; |
6
|
|
|
use PhpParser\Node; |
7
|
|
|
use PhpParser\Node\Stmt; |
8
|
|
|
use PHPSA\Analyzer\Pass; |
9
|
|
|
use PhpParser\Node\Expr\Array_; |
10
|
|
|
use PhpParser\Node\Expr\FuncCall; |
11
|
|
|
use PhpParser\Node\Scalar\String_; |
12
|
|
|
use PHPSA\Analyzer\Helper\DefaultMetadataPassTrait; |
13
|
|
|
|
14
|
|
|
class FunctionStringFormater extends AbstractFunctionCallAnalyzer |
15
|
|
|
{ |
16
|
|
|
use DefaultMetadataPassTrait; |
17
|
|
|
|
18
|
|
|
const DESCRIPTION = 'Format string has same number of placeholders as parameters are passed into and forbid invalid type formats.'; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* @var array functions |
22
|
|
|
*/ |
23
|
|
|
protected static $functions = [ |
24
|
|
|
'printf' => 'printf', |
25
|
|
|
'sprintf' => 'sprintf', |
26
|
|
|
'vprintf' => 'vprintf', |
27
|
|
|
'vsprintf' => 'vsprintf' |
28
|
|
|
]; |
29
|
|
|
/** |
30
|
|
|
* Placeholders for type format |
31
|
|
|
* @var array |
32
|
|
|
*/ |
33
|
|
|
protected $placeholders = []; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* @param FuncCall $funcCall |
37
|
|
|
* @param Context $context |
38
|
|
|
* @return bool |
39
|
|
|
*/ |
40
|
7 |
|
public function pass(FuncCall $funcCall, Context $context) |
41
|
|
|
{ |
42
|
7 |
|
$functionName = $this->resolveFunctionName($funcCall, $context); |
43
|
7 |
|
if ($functionName && isset(self::$functions[$functionName])) { |
44
|
1 |
|
if ($funcCall->args) { |
|
|
|
|
45
|
1 |
|
$args = $funcCall->args; |
46
|
|
|
|
47
|
1 |
|
if (! ($args[0]->value instanceof String_)) { |
48
|
1 |
|
$context->notice( |
49
|
1 |
|
'function_argument_invalid', |
50
|
1 |
|
sprintf('First parameter of %s must be string', $functionName), |
51
|
|
|
$funcCall |
52
|
1 |
|
); |
53
|
1 |
|
} |
54
|
|
|
|
55
|
1 |
|
if (($args[0]->value instanceof String_)) { |
56
|
1 |
|
$string = $args[0]->value->value; |
|
|
|
|
57
|
|
|
// get invalid placeholders |
58
|
1 |
|
preg_match_all("/(?<!\x25)\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?[^bcdeEufFgGosxX(%)]/", $string, $this->placeholders); |
59
|
1 |
|
if (count($this->placeholders[0]) > 0) { |
60
|
1 |
|
$context->notice( |
61
|
1 |
|
'function_format_type_invalid', |
62
|
1 |
|
sprintf('Unexpected type format in %s function string', $functionName), |
63
|
|
|
$funcCall |
64
|
1 |
|
); |
65
|
1 |
|
} else { |
66
|
|
|
// get valid placesholders |
67
|
1 |
|
preg_match_all("/(?<!\x25)\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([bcdeEufFgGosxX])/", $string, $this->placeholders); |
68
|
1 |
|
if ($args[1]->value instanceof Array_) { |
69
|
1 |
|
if (count($this->placeholders[0]) !== count($args[1]->value->items)) { |
|
|
|
|
70
|
1 |
|
$context->notice( |
71
|
1 |
|
'function_array_length_invalid', |
72
|
1 |
|
sprintf('Unexpected length of array passed to %s', $functionName), |
73
|
|
|
$funcCall |
74
|
1 |
|
); |
75
|
1 |
|
} |
76
|
1 |
|
} else { |
77
|
1 |
|
if (count($this->placeholders[0]) !== (count($args) - 1)) { |
78
|
1 |
|
$context->notice( |
79
|
1 |
|
'function_arguments_length_invalid', |
80
|
1 |
|
sprintf('Unexpected length of arguments passed to %s', $functionName), |
81
|
|
|
$funcCall |
82
|
1 |
|
); |
83
|
1 |
|
} |
84
|
|
|
} |
85
|
|
|
} |
86
|
1 |
|
} |
87
|
1 |
|
} |
88
|
1 |
|
} |
89
|
|
|
|
90
|
7 |
|
return true; |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* @return array |
95
|
|
|
*/ |
96
|
2 |
|
public function getRegister() |
97
|
|
|
{ |
98
|
|
|
return [ |
99
|
|
|
FuncCall::class |
100
|
2 |
|
]; |
101
|
|
|
} |
102
|
|
|
} |
103
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.