1<?php 2/** 3 * Squiz_Sniffs_PHP_InnerFunctionsSniff. 4 * 5 * PHP version 5 6 * 7 * @category PHP 8 * @package PHP_CodeSniffer 9 * @author Greg Sherwood <gsherwood@squiz.net> 10 * @author Marc McIntyre <mmcintyre@squiz.net> 11 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) 12 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 13 * @link http://pear.php.net/package/PHP_CodeSniffer 14 */ 15 16/** 17 * Squiz_Sniffs_PHP_NonExecutableCodeSniff. 18 * 19 * Warns about code that can never been executed. This happens when a function 20 * returns before the code, or a break ends execution of a statement etc. 21 * 22 * @category PHP 23 * @package PHP_CodeSniffer 24 * @author Greg Sherwood <gsherwood@squiz.net> 25 * @author Marc McIntyre <mmcintyre@squiz.net> 26 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) 27 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 28 * @version Release: @package_version@ 29 * @link http://pear.php.net/package/PHP_CodeSniffer 30 */ 31class Squiz_Sniffs_PHP_NonExecutableCodeSniff implements PHP_CodeSniffer_Sniff 32{ 33 34 35 /** 36 * Returns an array of tokens this test wants to listen for. 37 * 38 * @return array 39 */ 40 public function register() 41 { 42 return array( 43 T_BREAK, 44 T_CONTINUE, 45 T_RETURN, 46 T_THROW, 47 T_EXIT, 48 ); 49 50 }//end register() 51 52 53 /** 54 * Processes this test, when one of its tokens is encountered. 55 * 56 * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. 57 * @param int $stackPtr The position of the current token in 58 * the stack passed in $tokens. 59 * 60 * @return void 61 */ 62 public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) 63 { 64 $tokens = $phpcsFile->getTokens(); 65 66 // If this token is preceded with an "or", it only relates to one line 67 // and should be ignored. For example: fopen() or die(). 68 $prev = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr - 1), null, true); 69 if ($tokens[$prev]['code'] === T_LOGICAL_OR || $tokens[$prev]['code'] === T_BOOLEAN_OR) { 70 return; 71 } 72 73 // Check if this token is actually part of a one-line IF or ELSE statement. 74 for ($i = ($stackPtr - 1); $i > 0; $i--) { 75 if ($tokens[$i]['code'] === T_CLOSE_PARENTHESIS) { 76 $i = $tokens[$i]['parenthesis_opener']; 77 continue; 78 } else if (isset(PHP_CodeSniffer_Tokens::$emptyTokens[$tokens[$i]['code']]) === true) { 79 continue; 80 } 81 82 break; 83 } 84 85 if ($tokens[$i]['code'] === T_IF 86 || $tokens[$i]['code'] === T_ELSE 87 || $tokens[$i]['code'] === T_ELSEIF 88 ) { 89 return; 90 } 91 92 if ($tokens[$stackPtr]['code'] === T_RETURN) { 93 $next = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); 94 if ($tokens[$next]['code'] === T_SEMICOLON) { 95 $next = $phpcsFile->findNext(T_WHITESPACE, ($next + 1), null, true); 96 if ($tokens[$next]['code'] === T_CLOSE_CURLY_BRACKET) { 97 // If this is the closing brace of a function 98 // then this return statement doesn't return anything 99 // and is not required anyway. 100 $owner = $tokens[$next]['scope_condition']; 101 if ($tokens[$owner]['code'] === T_FUNCTION) { 102 $warning = 'Empty return statement not required here'; 103 $phpcsFile->addWarning($warning, $stackPtr, 'ReturnNotRequired'); 104 return; 105 } 106 } 107 } 108 } 109 110 if (isset($tokens[$stackPtr]['scope_opener']) === true) { 111 $owner = $tokens[$stackPtr]['scope_condition']; 112 if ($tokens[$owner]['code'] === T_CASE || $tokens[$owner]['code'] === T_DEFAULT) { 113 // This token closes the scope of a CASE or DEFAULT statement 114 // so any code between this statement and the next CASE, DEFAULT or 115 // end of SWITCH token will not be executable. 116 $end = $phpcsFile->findEndOfStatement($stackPtr); 117 $next = $phpcsFile->findNext( 118 array( 119 T_CASE, 120 T_DEFAULT, 121 T_CLOSE_CURLY_BRACKET, 122 ), 123 ($end + 1) 124 ); 125 126 if ($next !== false) { 127 $lastLine = $tokens[$end]['line']; 128 for ($i = ($stackPtr + 1); $i < $next; $i++) { 129 if (isset(PHP_CodeSniffer_Tokens::$emptyTokens[$tokens[$i]['code']]) === true) { 130 continue; 131 } 132 133 $line = $tokens[$i]['line']; 134 if ($line > $lastLine) { 135 $type = substr($tokens[$stackPtr]['type'], 2); 136 $warning = 'Code after %s statement cannot be executed'; 137 $data = array($type); 138 $phpcsFile->addWarning($warning, $i, 'Unreachable', $data); 139 $lastLine = $line; 140 } 141 } 142 }//end if 143 144 // That's all we have to check for these types of statements. 145 return; 146 }//end if 147 }//end if 148 149 // This token may be part of an inline condition. 150 // If we find a closing parenthesis that belongs to a condition 151 // we should ignore this token. 152 $prev = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr - 1), null, true); 153 if (isset($tokens[$prev]['parenthesis_owner']) === true) { 154 $owner = $tokens[$prev]['parenthesis_owner']; 155 $ignore = array( 156 T_IF => true, 157 T_ELSE => true, 158 T_ELSEIF => true, 159 ); 160 if (isset($ignore[$tokens[$owner]['code']]) === true) { 161 return; 162 } 163 } 164 165 $ourConditions = array_keys($tokens[$stackPtr]['conditions']); 166 167 if (empty($ourConditions) === false) { 168 $condition = array_pop($ourConditions); 169 170 if (isset($tokens[$condition]['scope_closer']) === false) { 171 return; 172 } 173 174 // Special case for BREAK statements sitting directly inside SWITCH 175 // statements. If we get to this point, we know the BREAK is not being 176 // used to close a CASE statement, so it is most likely non-executable 177 // code itself (as is the case when you put return; break; at the end of 178 // a case). So we need to ignore this token. 179 if ($tokens[$condition]['code'] === T_SWITCH 180 && $tokens[$stackPtr]['code'] === T_BREAK 181 ) { 182 return; 183 } 184 185 $closer = $tokens[$condition]['scope_closer']; 186 187 // If the closer for our condition is shared with other openers, 188 // we will need to throw errors from this token to the next 189 // shared opener (if there is one), not to the scope closer. 190 $nextOpener = null; 191 for ($i = ($stackPtr + 1); $i < $closer; $i++) { 192 if (isset($tokens[$i]['scope_closer']) === true) { 193 if ($tokens[$i]['scope_closer'] === $closer) { 194 // We found an opener that shares the same 195 // closing token as us. 196 $nextOpener = $i; 197 break; 198 } 199 } 200 }//end for 201 202 if ($nextOpener === null) { 203 $end = $closer; 204 } else { 205 $end = ($nextOpener - 1); 206 } 207 } else { 208 // This token is in the global scope. 209 if ($tokens[$stackPtr]['code'] === T_BREAK) { 210 return; 211 } 212 213 // Throw an error for all lines until the end of the file. 214 $end = ($phpcsFile->numTokens - 1); 215 }//end if 216 217 // Find the semicolon that ends this statement, skipping 218 // nested statements like FOR loops and closures. 219 for ($start = ($stackPtr + 1); $start < $phpcsFile->numTokens; $start++) { 220 if ($start === $end) { 221 break; 222 } 223 224 if ($tokens[$start]['code'] === T_OPEN_PARENTHESIS) { 225 $start = $tokens[$start]['parenthesis_closer']; 226 continue; 227 } 228 229 if ($tokens[$start]['code'] === T_OPEN_CURLY_BRACKET) { 230 $start = $tokens[$start]['bracket_closer']; 231 continue; 232 } 233 234 if ($tokens[$start]['code'] === T_SEMICOLON) { 235 break; 236 } 237 }//end for 238 239 $lastLine = $tokens[$start]['line']; 240 for ($i = ($start + 1); $i < $end; $i++) { 241 if (isset(PHP_CodeSniffer_Tokens::$emptyTokens[$tokens[$i]['code']]) === true 242 || isset(PHP_CodeSniffer_Tokens::$bracketTokens[$tokens[$i]['code']]) === true 243 ) { 244 continue; 245 } 246 247 // Skip whole functions and classes/interfaces because they are not 248 // technically executed code, but rather declarations that may be used. 249 if ($tokens[$i]['code'] === T_FUNCTION 250 || $tokens[$i]['code'] === T_CLASS 251 || $tokens[$i]['code'] === T_INTERFACE 252 ) { 253 $i = $tokens[$i]['scope_closer']; 254 continue; 255 } 256 257 $line = $tokens[$i]['line']; 258 if ($line > $lastLine) { 259 $type = substr($tokens[$stackPtr]['type'], 2); 260 $warning = 'Code after %s statement cannot be executed'; 261 $data = array($type); 262 $phpcsFile->addWarning($warning, $i, 'Unreachable', $data); 263 $lastLine = $line; 264 } 265 }//end for 266 267 }//end process() 268 269 270}//end class 271