1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* \PHPCompatibility\Sniffs\PHP\ForbiddenCallTimePassByReference. |
4
|
|
|
* |
5
|
|
|
* PHP version 5.4 |
6
|
|
|
* |
7
|
|
|
* @category PHP |
8
|
|
|
* @package PHPCompatibility |
9
|
|
|
* @author Gary Rogers <[email protected]> |
10
|
|
|
* @author Florian Grandel <[email protected]> |
11
|
|
|
* @copyright 2009 Florian Grandel |
12
|
|
|
*/ |
13
|
|
|
|
14
|
|
|
namespace PHPCompatibility\Sniffs\PHP; |
15
|
|
|
|
16
|
|
|
use PHPCompatibility\Sniff; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* \PHPCompatibility\Sniffs\PHP\ForbiddenCallTimePassByReference. |
20
|
|
|
* |
21
|
|
|
* Discourages the use of call time pass by references |
22
|
|
|
* |
23
|
|
|
* PHP version 5.4 |
24
|
|
|
* |
25
|
|
|
* @category PHP |
26
|
|
|
* @package PHPCompatibility |
27
|
|
|
* @author Gary Rogers <[email protected]> |
28
|
|
|
* @author Florian Grandel <[email protected]> |
29
|
|
|
* @copyright 2009 Florian Grandel |
30
|
|
|
*/ |
31
|
|
|
class ForbiddenCallTimePassByReferenceSniff extends Sniff |
32
|
|
|
{ |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* Tokens that represent assignments or equality comparisons. |
36
|
|
|
* |
37
|
|
|
* Near duplicate of Tokens::$assignmentTokens + Tokens::$equalityTokens. |
38
|
|
|
* Copied in for PHPCS cross-version compatibility. |
39
|
|
|
* |
40
|
|
|
* @var array |
41
|
|
|
*/ |
42
|
|
|
private $assignOrCompare = array( |
43
|
|
|
// Comparison tokens. |
44
|
|
|
'T_IS_EQUAL' => true, |
45
|
|
|
'T_IS_NOT_EQUAL' => true, |
46
|
|
|
'T_IS_IDENTICAL' => true, |
47
|
|
|
'T_IS_NOT_IDENTICAL' => true, |
48
|
|
|
'T_IS_SMALLER_OR_EQUAL' => true, |
49
|
|
|
'T_IS_GREATER_OR_EQUAL' => true, |
50
|
|
|
|
51
|
|
|
// Assignment tokens. |
52
|
|
|
'T_EQUAL' => true, |
53
|
|
|
'T_AND_EQUAL' => true, |
54
|
|
|
'T_OR_EQUAL' => true, |
55
|
|
|
'T_CONCAT_EQUAL' => true, |
56
|
|
|
'T_DIV_EQUAL' => true, |
57
|
|
|
'T_MINUS_EQUAL' => true, |
58
|
|
|
'T_POW_EQUAL' => true, |
59
|
|
|
'T_MOD_EQUAL' => true, |
60
|
|
|
'T_MUL_EQUAL' => true, |
61
|
|
|
'T_PLUS_EQUAL' => true, |
62
|
|
|
'T_XOR_EQUAL' => true, |
63
|
|
|
'T_DOUBLE_ARROW' => true, |
64
|
|
|
'T_SL_EQUAL' => true, |
65
|
|
|
'T_SR_EQUAL' => true, |
66
|
|
|
'T_COALESCE_EQUAL' => true, |
67
|
|
|
'T_ZSR_EQUAL' => true, |
68
|
|
|
); |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* Returns an array of tokens this test wants to listen for. |
72
|
|
|
* |
73
|
|
|
* @return array |
74
|
|
|
*/ |
75
|
|
|
public function register() |
76
|
|
|
{ |
77
|
|
|
return array(T_STRING); |
78
|
|
|
|
79
|
|
|
}//end register() |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* Processes this test, when one of its tokens is encountered. |
83
|
|
|
* |
84
|
|
|
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned. |
85
|
|
|
* @param int $stackPtr The position of the current token |
86
|
|
|
* in the stack passed in $tokens. |
87
|
|
|
* |
88
|
|
|
* @return void |
89
|
|
|
*/ |
90
|
|
|
public function process(\PHP_CodeSniffer_File $phpcsFile, $stackPtr) |
91
|
|
|
{ |
92
|
|
|
if ($this->supportsAbove('5.3') === false) { |
93
|
|
|
return; |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
$tokens = $phpcsFile->getTokens(); |
97
|
|
|
|
98
|
|
|
// Skip tokens that are the names of functions or classes |
99
|
|
|
// within their definitions. For example: function myFunction... |
100
|
|
|
// "myFunction" is T_STRING but we should skip because it is not a |
101
|
|
|
// function or method *call*. |
102
|
|
|
$findTokens = \PHP_CodeSniffer_Tokens::$emptyTokens; |
103
|
|
|
$findTokens[] = T_BITWISE_AND; |
104
|
|
|
|
105
|
|
|
$prevNonEmpty = $phpcsFile->findPrevious( |
106
|
|
|
$findTokens, |
107
|
|
|
($stackPtr - 1), |
108
|
|
|
null, |
109
|
|
|
true |
110
|
|
|
); |
111
|
|
|
|
112
|
|
|
if ($prevNonEmpty !== false && in_array($tokens[$prevNonEmpty]['type'], array('T_FUNCTION', 'T_CLASS', 'T_INTERFACE', 'T_TRAIT'), true)) { |
113
|
|
|
return; |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
// If the next non-whitespace token after the function or method call |
117
|
|
|
// is not an opening parenthesis then it can't really be a *call*. |
118
|
|
|
$openBracket = $phpcsFile->findNext( |
119
|
|
|
\PHP_CodeSniffer_Tokens::$emptyTokens, |
120
|
|
|
($stackPtr + 1), |
121
|
|
|
null, |
122
|
|
|
true |
123
|
|
|
); |
124
|
|
|
|
125
|
|
|
if ($openBracket === false || $tokens[$openBracket]['code'] !== T_OPEN_PARENTHESIS |
126
|
|
|
|| isset($tokens[$openBracket]['parenthesis_closer']) === false |
127
|
|
|
) { |
128
|
|
|
return; |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
// Get the function call parameters. |
132
|
|
|
$parameters = $this->getFunctionCallParameters($phpcsFile, $stackPtr); |
133
|
|
|
if (count($parameters) === 0) { |
134
|
|
|
return; |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
// Which nesting level is the one we are interested in ? |
138
|
|
|
$nestedParenthesisCount = 1; |
139
|
|
View Code Duplication |
if (isset($tokens[$openBracket]['nested_parenthesis'])) { |
140
|
|
|
$nestedParenthesisCount = count($tokens[$openBracket]['nested_parenthesis']) + 1; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
foreach ($parameters as $parameter) { |
144
|
|
|
if ($this->isCallTimePassByReferenceParam($phpcsFile, $parameter, $nestedParenthesisCount) === true) { |
145
|
|
|
// T_BITWISE_AND represents a pass-by-reference. |
146
|
|
|
$error = 'Using a call-time pass-by-reference is deprecated since PHP 5.3'; |
147
|
|
|
$isError = false; |
148
|
|
|
$errorCode = 'Deprecated'; |
149
|
|
|
|
150
|
|
|
if ($this->supportsAbove('5.4')) { |
151
|
|
|
$error .= ' and prohibited since PHP 5.4'; |
152
|
|
|
$isError = true; |
153
|
|
|
$errorCode = 'NotAllowed'; |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
$this->addMessage($phpcsFile, $error, $parameter['start'], $isError, $errorCode); |
157
|
|
|
} |
158
|
|
|
} |
159
|
|
|
}//end process() |
160
|
|
|
|
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* Determine whether a parameter is passed by reference. |
164
|
|
|
* |
165
|
|
|
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned. |
166
|
|
|
* @param array $parameter Information on the current parameter |
167
|
|
|
* to be examined. |
168
|
|
|
* @param int $nestingLevel Target nesting level. |
169
|
|
|
* |
170
|
|
|
* @return bool |
171
|
|
|
*/ |
172
|
|
|
protected function isCallTimePassByReferenceParam(\PHP_CodeSniffer_File $phpcsFile, $parameter, $nestingLevel) |
173
|
|
|
{ |
174
|
|
|
$tokens = $phpcsFile->getTokens(); |
175
|
|
|
|
176
|
|
|
$searchStartToken = $parameter['start'] - 1; |
177
|
|
|
$searchEndToken = $parameter['end'] + 1; |
178
|
|
|
$nextVariable = $searchStartToken; |
179
|
|
|
do { |
180
|
|
|
$nextVariable = $phpcsFile->findNext(T_VARIABLE, ($nextVariable + 1), $searchEndToken); |
181
|
|
|
if ($nextVariable === false) { |
182
|
|
|
return false; |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
// Make sure the variable belongs directly to this function call |
186
|
|
|
// and is not inside a nested function call or array. |
187
|
|
|
if (isset($tokens[$nextVariable]['nested_parenthesis']) === false |
188
|
|
|
|| (count($tokens[$nextVariable]['nested_parenthesis']) !== $nestingLevel) |
189
|
|
|
) { |
190
|
|
|
continue; |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
// Checking this: $value = my_function(...[*]$arg...). |
|
|
|
|
194
|
|
|
$tokenBefore = $phpcsFile->findPrevious( |
195
|
|
|
\PHP_CodeSniffer_Tokens::$emptyTokens, |
196
|
|
|
($nextVariable - 1), |
197
|
|
|
$searchStartToken, |
198
|
|
|
true |
199
|
|
|
); |
200
|
|
|
|
201
|
|
|
if ($tokenBefore === false || $tokens[$tokenBefore]['code'] !== T_BITWISE_AND) { |
202
|
|
|
// Nothing before the token or no &. |
203
|
|
|
continue; |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
if ($phpcsFile->isReference($tokenBefore) === false) { |
207
|
|
|
continue; |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
// Checking this: $value = my_function(...[*]&$arg...). |
|
|
|
|
211
|
|
|
$tokenBefore = $phpcsFile->findPrevious( |
212
|
|
|
\PHP_CodeSniffer_Tokens::$emptyTokens, |
213
|
|
|
($tokenBefore - 1), |
214
|
|
|
$searchStartToken, |
215
|
|
|
true |
216
|
|
|
); |
217
|
|
|
|
218
|
|
|
// Prevent false positive on assign by reference and compare with reference |
219
|
|
|
// within function call parameters. |
220
|
|
|
if (isset($this->assignOrCompare[$tokens[$tokenBefore]['type']])) { |
221
|
|
|
continue; |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
// The found T_BITWISE_AND represents a pass-by-reference. |
225
|
|
|
return true; |
226
|
|
|
|
227
|
|
|
} while ($nextVariable < $searchEndToken); |
228
|
|
|
|
229
|
|
|
// This code should never be reached, but here in case of weird bugs ;-) |
230
|
|
|
return false; |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
}//end class |
234
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.