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