1<?php 2/** 3 * Generic_Sniffs_Functions_CallTimePassByReferenceSniff. 4 * 5 * PHP version 5 6 * 7 * @category PHP 8 * @package PHP_CodeSniffer 9 * @author Florian Grandel <jerico.dev@gmail.com> 10 * @copyright 2009-2014 Florian Grandel 11 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 12 * @link http://pear.php.net/package/PHP_CodeSniffer 13 */ 14 15/** 16 * Generic_Sniffs_Functions_CallTimePassByReferenceSniff. 17 * 18 * Ensures that variables are not passed by reference when calling a function. 19 * 20 * @category PHP 21 * @package PHP_CodeSniffer 22 * @author Florian Grandel <jerico.dev@gmail.com> 23 * @copyright 2009-2014 Florian Grandel 24 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 25 * @version Release: @package_version@ 26 * @link http://pear.php.net/package/PHP_CodeSniffer 27 */ 28class Generic_Sniffs_Functions_CallTimePassByReferenceSniff implements PHP_CodeSniffer_Sniff 29{ 30 31 32 /** 33 * Returns an array of tokens this test wants to listen for. 34 * 35 * @return array 36 */ 37 public function register() 38 { 39 return array( 40 T_STRING, 41 T_VARIABLE, 42 ); 43 44 }//end register() 45 46 47 /** 48 * Processes this test, when one of its tokens is encountered. 49 * 50 * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. 51 * @param int $stackPtr The position of the current token 52 * in the stack passed in $tokens. 53 * 54 * @return void 55 */ 56 public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) 57 { 58 $tokens = $phpcsFile->getTokens(); 59 60 $findTokens = array_merge( 61 PHP_CodeSniffer_Tokens::$emptyTokens, 62 array(T_BITWISE_AND) 63 ); 64 65 $prev = $phpcsFile->findPrevious($findTokens, ($stackPtr - 1), null, true); 66 67 // Skip tokens that are the names of functions or classes 68 // within their definitions. For example: function myFunction... 69 // "myFunction" is T_STRING but we should skip because it is not a 70 // function or method *call*. 71 $prevCode = $tokens[$prev]['code']; 72 if ($prevCode === T_FUNCTION || $prevCode === T_CLASS) { 73 return; 74 } 75 76 // If the next non-whitespace token after the function or method call 77 // is not an opening parenthesis then it cant really be a *call*. 78 $functionName = $stackPtr; 79 $openBracket = $phpcsFile->findNext( 80 PHP_CodeSniffer_Tokens::$emptyTokens, 81 ($functionName + 1), 82 null, 83 true 84 ); 85 86 if ($tokens[$openBracket]['code'] !== T_OPEN_PARENTHESIS) { 87 return; 88 } 89 90 if (isset($tokens[$openBracket]['parenthesis_closer']) === false) { 91 return; 92 } 93 94 $closeBracket = $tokens[$openBracket]['parenthesis_closer']; 95 96 $nextSeparator = $openBracket; 97 $find = array( 98 T_VARIABLE, 99 T_OPEN_SHORT_ARRAY, 100 ); 101 102 while (($nextSeparator = $phpcsFile->findNext($find, ($nextSeparator + 1), $closeBracket)) !== false) { 103 if (isset($tokens[$nextSeparator]['nested_parenthesis']) === false) { 104 continue; 105 } 106 107 if ($tokens[$nextSeparator]['code'] === T_OPEN_SHORT_ARRAY) { 108 $nextSeparator = $tokens[$nextSeparator]['bracket_closer']; 109 continue; 110 } 111 112 // Make sure the variable belongs directly to this function call 113 // and is not inside a nested function call or array. 114 $brackets = $tokens[$nextSeparator]['nested_parenthesis']; 115 $lastBracket = array_pop($brackets); 116 if ($lastBracket !== $closeBracket) { 117 continue; 118 } 119 120 // Checking this: $value = my_function(...[*]$arg...). 121 $tokenBefore = $phpcsFile->findPrevious( 122 PHP_CodeSniffer_Tokens::$emptyTokens, 123 ($nextSeparator - 1), 124 null, 125 true 126 ); 127 128 if ($tokens[$tokenBefore]['code'] === T_BITWISE_AND) { 129 // Checking this: $value = my_function(...[*]&$arg...). 130 $tokenBefore = $phpcsFile->findPrevious( 131 PHP_CodeSniffer_Tokens::$emptyTokens, 132 ($tokenBefore - 1), 133 null, 134 true 135 ); 136 137 // We have to exclude all uses of T_BITWISE_AND that are not 138 // references. We use a blacklist approach as we prefer false 139 // positives to not identifying a pass-by-reference call at all. 140 $tokenCode = $tokens[$tokenBefore]['code']; 141 if ($tokenCode === T_VARIABLE 142 || $tokenCode === T_CLOSE_PARENTHESIS 143 || $tokenCode === T_CLOSE_SQUARE_BRACKET 144 || $tokenCode === T_LNUMBER 145 || isset(PHP_CodeSniffer_Tokens::$assignmentTokens[$tokenCode]) === true 146 ) { 147 continue; 148 } 149 150 // T_BITWISE_AND represents a pass-by-reference. 151 $error = 'Call-time pass-by-reference calls are prohibited'; 152 $phpcsFile->addError($error, $tokenBefore, 'NotAllowed'); 153 }//end if 154 }//end while 155 156 }//end process() 157 158 159}//end class 160