From 89ec41b89137851f9d865d19255dafed3ce80a1a Mon Sep 17 00:00:00 2001 From: Ulrich Pogson Date: Sun, 12 Feb 2017 20:25:31 +0100 Subject: [PATCH] Start checking callbacks for restricted functions --- .../AbstractFunctionRestrictionsSniff.php | 110 +++++++++++++++++- .../Functions/FunctionRestrictionsSniff.php | 10 +- .../FunctionRestrictionsUnitTest.inc | 36 ++++++ .../FunctionRestrictionsUnitTest.php | 41 +++++++ 4 files changed, 195 insertions(+), 2 deletions(-) create mode 100644 WordPress/Tests/Functions/FunctionRestrictionsUnitTest.inc create mode 100644 WordPress/Tests/Functions/FunctionRestrictionsUnitTest.php diff --git a/WordPress/AbstractFunctionRestrictionsSniff.php b/WordPress/AbstractFunctionRestrictionsSniff.php index 2730d0d43b..cac1c564f9 100644 --- a/WordPress/AbstractFunctionRestrictionsSniff.php +++ b/WordPress/AbstractFunctionRestrictionsSniff.php @@ -68,6 +68,49 @@ abstract class WordPress_AbstractFunctionRestrictionsSniff extends WordPress_Sni */ protected $excluded_groups = array(); + /** + * List of known PHP and WP function which take a callback as an argument. + * + * @since 0.11.0 + * + * @var array => + */ + protected $callback_functions = array( + 'add_filter' => array( 2, ), + 'add_action' => array( 2, ), + 'call_user_func' => array( 1, ), + 'call_user_func_array' => array( 1, ), + 'forward_static_call' => array( 1, ), + 'forward_static_call_array' => array( 1, ), + 'array_diff_uassoc' => array( -1, ), // = last argument passed. + 'array_diff_ukey' => array( -1, ), // = last argument passed. + 'array_filter' => array( 2, ), + 'array_intersect_uassoc' => array( -1, ), // = last argument passed. + 'array_intersect_ukey' => array( -1, ), // = last argument passed. + 'array_map' => array( 1, ), + 'array_reduce' => array( 2, ), + 'array_udiff_assoc' => array( -1, ), // = last argument passed. + 'array_udiff_uassoc' => array( -1, -2, ), // = last argument passed. + 'array_udiff' => array( -1, ), // = last argument passed. + 'array_uintersect_assoc' => array( -1, ), // = last argument passed. + 'array_uintersect_uassoc' => array( -1, -2, ), // = last argument passed. + 'array_uintersect' => array( -1, ), // = last argument passed. + 'array_walk' => array( 2, ), + 'array_walk_recursive' => array( 2, ), + 'iterator_apply' => array( 2, ), + 'usort' => array( 2, ), + 'uasort' => array( 2, ), + 'uksort' => array( 2, ), + 'preg_replace_callback' => array( 2, ), + 'mb_ereg_replace_callback' => array( 2, ), + 'header_register_callback' => array( 1, ), + 'ob_start' => array( 1, ), + 'set_error_handler' => array( 1, ), + 'set_exception_handler' => array( 1, ), + 'register_shutdown_function' => array( 1, ), + 'register_tick_function' => array( 1, ), + ); + /** * Groups of functions to restrict. * @@ -170,7 +213,14 @@ public function process( PHP_CodeSniffer_File $phpcsFile, $stackPtr ) { $this->init( $phpcsFile ); if ( true === $this->is_targetted_token( $stackPtr ) ) { + + $callback_matches = $this->check_for_callback_matches( $stackPtr ); + if ( $callback_matches ) { + return $callback_matches; + } + return $this->check_for_matches( $stackPtr ); + } } // End process(). @@ -229,7 +279,7 @@ public function is_targetted_token( $stackPtr ) { * normal file processing. */ public function check_for_matches( $stackPtr ) { - $token_content = strtolower( $this->tokens[ $stackPtr ]['content'] ); + $token_content = strtolower( $this->strip_quotes( $this->tokens[ $stackPtr ]['content'] ) ); $skip_to = array(); foreach ( $this->groups as $groupName => $group ) { @@ -255,6 +305,64 @@ public function check_for_matches( $stackPtr ) { } // End check_for_matches(). + /** + * Verify if the current token is one of the targetted functions as callback. + * + * @since 0.11.0 + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return int|false + */ + public function check_for_callback_matches( $stackPtr ) { + + $token_content = strtolower( $this->tokens[ $stackPtr ]['content'] ); + + // Check if the function is used as a callback. + if ( ! isset( $this->callback_functions[ $token_content ] ) ) { + return false; + } + + $skip_to = array(); + $parameters = $this->get_function_call_parameters( $stackPtr ); + $positions = $this->callback_functions[ $token_content ]; + + foreach ( $positions as $position ) { + // Calculate the last argument if the position is negative. + + if ( $position < 0 ) { + $position = count( $parameters ) + 1 + $position; + } + + if ( ! isset( $parameters[ $position ] ) ) { + return false; + } + + // Only get function name not anonymous funtions. + $callback = $this->phpcsFile->findNext( + array( T_CONSTANT_ENCAPSED_STRING ), + $parameters[ $position ]['start'], + null, + false, + null, + true + ); + + if ( ! $callback ) { + return false; + } + + $skip_to[] = $this->check_for_matches( $callback ); + } + + if ( empty( $skip_to ) || min( $skip_to ) === 0 ) { + return false; + } + + return min( $skip_to ); + + } + /** * Process a matched token. * diff --git a/WordPress/Sniffs/Functions/FunctionRestrictionsSniff.php b/WordPress/Sniffs/Functions/FunctionRestrictionsSniff.php index f32bfe9234..c1f613d799 100644 --- a/WordPress/Sniffs/Functions/FunctionRestrictionsSniff.php +++ b/WordPress/Sniffs/Functions/FunctionRestrictionsSniff.php @@ -36,7 +36,15 @@ class WordPress_Sniffs_Functions_FunctionRestrictionsSniff extends WordPress_Abs * @return array */ public function getGroups() { - return array(); + return array( + 'foobar' => array( + 'type' => 'warning', + 'message' => 'Callback functions test.', + 'functions' => array( + 'foobar*', + ), + ), + ); } } // End class. diff --git a/WordPress/Tests/Functions/FunctionRestrictionsUnitTest.inc b/WordPress/Tests/Functions/FunctionRestrictionsUnitTest.inc new file mode 100644 index 0000000000..df683a1450 --- /dev/null +++ b/WordPress/Tests/Functions/FunctionRestrictionsUnitTest.inc @@ -0,0 +1,36 @@ + => + */ + public function getErrorList() { + return array(); + + } + + /** + * Returns the lines where warnings should occur. + * + * @return array => + */ + public function getWarningList() { + $array = array_fill( 4, 33, 1 ); + $array[18] = 2; + $array[21] = 2; + return $array; + + } + +} // End class.