Skip to content

Commit

Permalink
Start checking callbacks for restricted functions
Browse files Browse the repository at this point in the history
  • Loading branch information
grappler committed Feb 12, 2017
1 parent f2e97ba commit 433ff15
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 2 deletions.
110 changes: 109 additions & 1 deletion WordPress/AbstractFunctionRestrictionsSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 <string function name> => <int callback argument position>
*/
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.
*
Expand Down Expand Up @@ -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().
Expand Down Expand Up @@ -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 ) {
Expand All @@ -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.
*
Expand Down
10 changes: 9 additions & 1 deletion WordPress/Sniffs/Functions/FunctionRestrictionsSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
36 changes: 36 additions & 0 deletions WordPress/Tests/Functions/FunctionRestrictionsUnitTest.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

// Warnings.
add_filter( '', 'foobar' );
add_action( '', 'foobar' );
call_user_func( 'foobar' );
call_user_func_array( 'foobar' );
forward_static_call( 'foobar' );
forward_static_call_array( 'foobar' );
array_diff_uassoc( '', '', 'foobar' );
array_diff_ukey( '', '', 'foobar' );
array_filter( '', 'foobar' );
array_intersect_uassoc( '', 'foobar' );
array_intersect_ukey( '', 'foobar' );
array_map( 'foobar' );
array_reduce( '', 'foobar' );
array_udiff_assoc( '', 'foobar' );
array_udiff_uassoc( '', 'foobar1', 'foobar2' );
array_udiff( '', 'foobar' );
array_uintersect_assoc( '', 'foobar' );
array_uintersect_uassoc( '', 'foobar1', 'foobar2' );
array_uintersect( '', 'foobar' );
array_walk( '', 'foobar' );
array_walk_recursive( '', 'foobar' );
iterator_apply( '', 'foobar' );
usort( '', 'foobar' );
uasort( '', 'foobar' );
uksort( '', 'foobar' );
preg_replace_callback( '', 'foobar' );
mb_ereg_replace_callback( '', 'foobar' );
header_register_callback( 'foobar' );
ob_start( 'foobar' );
set_error_handler( 'foobar' );
set_exception_handler( 'foobar' );
register_shutdown_function( 'foobar' );
register_tick_function( 'foobar' );
41 changes: 41 additions & 0 deletions WordPress/Tests/Functions/FunctionRestrictionsUnitTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php
/**
* Unit test class for WordPress Coding Standard.
*
* @package WPCS\WordPressCodingStandards
* @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards
* @license https://opensource.org/licenses/MIT MIT
*/

/**
* Unit test class for the DontExtract sniff.
*
* @package WPCS\WordPressCodingStandards
* @since 0.10.0
*/
class WordPress_Tests_Functions_FunctionRestrictionsUnitTest extends AbstractSniffUnitTest {

/**
* Returns the lines where errors should occur.
*
* @return array <int line number> => <int number of errors>
*/
public function getErrorList() {
return array();

}

/**
* Returns the lines where warnings should occur.
*
* @return array <int line number> => <int number of warnings>
*/
public function getWarningList() {
$array = array_fill( 4, 33, 1 );
$array[18] = 2;
$array[21] = 2;
return $array;

}

} // End class.

0 comments on commit 433ff15

Please sign in to comment.