Skip to content

Commit 360566a

Browse files
committed
Start checking callbacks for restricted functions
1 parent f2e97ba commit 360566a

File tree

4 files changed

+197
-2
lines changed

4 files changed

+197
-2
lines changed

WordPress/AbstractFunctionRestrictionsSniff.php

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,51 @@ abstract class WordPress_AbstractFunctionRestrictionsSniff extends WordPress_Sni
6868
*/
6969
protected $excluded_groups = array();
7070

71+
/**
72+
* List of known PHP and WP function which take a callback as an argument.
73+
*
74+
* Sorted alphabetically. Last updated on 8th March 2017.
75+
*
76+
* @since 0.11.0
77+
*
78+
* @var array <string function name> => <int callback argument position>
79+
*/
80+
protected $callback_functions = array(
81+
'add_filter' => array( 2 ),
82+
'add_action' => array( 2 ),
83+
'array_diff_uassoc' => array( -1 ), // = last argument passed.
84+
'array_diff_ukey' => array( -1 ), // = last argument passed.
85+
'array_filter' => array( 2 ),
86+
'array_intersect_uassoc' => array( -1 ), // = last argument passed.
87+
'array_intersect_ukey' => array( -1 ), // = last argument passed.
88+
'array_map' => array( 1 ),
89+
'array_reduce' => array( 2 ),
90+
'array_udiff_assoc' => array( -1 ), // = last argument passed.
91+
'array_udiff_uassoc' => array( -1, -2 ), // = last argument passed.
92+
'array_udiff' => array( -1 ), // = last argument passed.
93+
'array_uintersect_assoc' => array( -1 ), // = last argument passed.
94+
'array_uintersect_uassoc' => array( -1, -2 ), // = last argument passed.
95+
'array_uintersect' => array( -1 ), // = last argument passed.
96+
'array_walk' => array( 2 ),
97+
'array_walk_recursive' => array( 2 ),
98+
'call_user_func' => array( 1 ),
99+
'call_user_func_array' => array( 1 ),
100+
'forward_static_call' => array( 1 ),
101+
'forward_static_call_array' => array( 1 ),
102+
'header_register_callback' => array( 1 ),
103+
'iterator_apply' => array( 2 ),
104+
'mb_ereg_replace_callback' => array( 2 ),
105+
'ob_start' => array( 1 ),
106+
'preg_replace_callback' => array( 2 ),
107+
'register_shutdown_function' => array( 1 ),
108+
'register_tick_function' => array( 1 ),
109+
'set_error_handler' => array( 1 ),
110+
'set_exception_handler' => array( 1 ),
111+
'uasort' => array( 2 ),
112+
'uksort' => array( 2 ),
113+
'usort' => array( 2 ),
114+
);
115+
71116
/**
72117
* Groups of functions to restrict.
73118
*
@@ -170,7 +215,14 @@ public function process( PHP_CodeSniffer_File $phpcsFile, $stackPtr ) {
170215
$this->init( $phpcsFile );
171216

172217
if ( true === $this->is_targetted_token( $stackPtr ) ) {
218+
219+
$callback_matches = $this->check_for_callback_matches( $stackPtr );
220+
if ( $callback_matches ) {
221+
return $callback_matches;
222+
}
223+
173224
return $this->check_for_matches( $stackPtr );
225+
174226
}
175227

176228
} // End process().
@@ -229,7 +281,7 @@ public function is_targetted_token( $stackPtr ) {
229281
* normal file processing.
230282
*/
231283
public function check_for_matches( $stackPtr ) {
232-
$token_content = strtolower( $this->tokens[ $stackPtr ]['content'] );
284+
$token_content = strtolower( $this->strip_quotes( $this->tokens[ $stackPtr ]['content'] ) );
233285
$skip_to = array();
234286

235287
foreach ( $this->groups as $groupName => $group ) {
@@ -255,6 +307,64 @@ public function check_for_matches( $stackPtr ) {
255307

256308
} // End check_for_matches().
257309

310+
/**
311+
* Verify if the current token is one of the targetted functions as callback.
312+
*
313+
* @since 0.11.0
314+
*
315+
* @param int $stackPtr The position of the current token in the stack.
316+
*
317+
* @return int|false
318+
*/
319+
public function check_for_callback_matches( $stackPtr ) {
320+
321+
$token_content = strtolower( $this->tokens[ $stackPtr ]['content'] );
322+
323+
// Check if the function is used as a callback.
324+
if ( ! isset( $this->callback_functions[ $token_content ] ) ) {
325+
return false;
326+
}
327+
328+
$skip_to = array();
329+
$parameters = $this->get_function_call_parameters( $stackPtr );
330+
$positions = $this->callback_functions[ $token_content ];
331+
332+
foreach ( $positions as $position ) {
333+
334+
// Calculate the last argument if the position is negative.
335+
if ( $position < 0 ) {
336+
$position = count( $parameters ) + 1 + $position;
337+
}
338+
339+
if ( ! isset( $parameters[ $position ] ) ) {
340+
return false;
341+
}
342+
343+
// Only get function name not anonymous funtions.
344+
$callback = $this->phpcsFile->findNext(
345+
array( T_CONSTANT_ENCAPSED_STRING ),
346+
$parameters[ $position ]['start'],
347+
null,
348+
false,
349+
null,
350+
true
351+
);
352+
353+
if ( ! $callback ) {
354+
return false;
355+
}
356+
357+
$skip_to[] = $this->check_for_matches( $callback );
358+
}
359+
360+
if ( empty( $skip_to ) || min( $skip_to ) === 0 ) {
361+
return false;
362+
}
363+
364+
return min( $skip_to );
365+
366+
}
367+
258368
/**
259369
* Process a matched token.
260370
*

WordPress/Sniffs/Functions/FunctionRestrictionsSniff.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,15 @@ class WordPress_Sniffs_Functions_FunctionRestrictionsSniff extends WordPress_Abs
3636
* @return array
3737
*/
3838
public function getGroups() {
39-
return array();
39+
return array(
40+
'foobar' => array(
41+
'type' => 'warning',
42+
'message' => 'Callback functions test.',
43+
'functions' => array(
44+
'foobar*',
45+
),
46+
),
47+
);
4048
}
4149

4250
} // End class.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
// Warnings.
4+
add_filter( '', 'foobar' );
5+
add_action( '', 'foobar' );
6+
call_user_func( 'foobar' );
7+
call_user_func_array( 'foobar' );
8+
forward_static_call( 'foobar' );
9+
forward_static_call_array( 'foobar' );
10+
array_diff_uassoc( '', '', 'foobar' );
11+
array_diff_ukey( '', '', 'foobar' );
12+
array_filter( '', 'foobar' );
13+
array_intersect_uassoc( '', 'foobar' );
14+
array_intersect_ukey( '', 'foobar' );
15+
array_map( 'foobar' );
16+
array_reduce( '', 'foobar' );
17+
array_udiff_assoc( '', 'foobar' );
18+
array_udiff_uassoc( '', 'foobar1', 'foobar2' );
19+
array_udiff( '', 'foobar' );
20+
array_uintersect_assoc( '', 'foobar' );
21+
array_uintersect_uassoc( '', 'foobar1', 'foobar2' );
22+
array_uintersect( '', 'foobar' );
23+
array_walk( '', 'foobar' );
24+
array_walk_recursive( '', 'foobar' );
25+
iterator_apply( '', 'foobar' );
26+
usort( '', 'foobar' );
27+
uasort( '', 'foobar' );
28+
uksort( '', 'foobar' );
29+
preg_replace_callback( '', 'foobar' );
30+
mb_ereg_replace_callback( '', 'foobar' );
31+
header_register_callback( 'foobar' );
32+
ob_start( 'foobar' );
33+
set_error_handler( 'foobar' );
34+
set_exception_handler( 'foobar' );
35+
register_shutdown_function( 'foobar' );
36+
register_tick_function( 'foobar' );
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
/**
3+
* Unit test class for WordPress Coding Standard.
4+
*
5+
* @package WPCS\WordPressCodingStandards
6+
* @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards
7+
* @license https://opensource.org/licenses/MIT MIT
8+
*/
9+
10+
/**
11+
* Unit test class for the DontExtract sniff.
12+
*
13+
* @package WPCS\WordPressCodingStandards
14+
* @since 0.10.0
15+
*/
16+
class WordPress_Tests_Functions_FunctionRestrictionsUnitTest extends AbstractSniffUnitTest {
17+
18+
/**
19+
* Returns the lines where errors should occur.
20+
*
21+
* @return array <int line number> => <int number of errors>
22+
*/
23+
public function getErrorList() {
24+
return array();
25+
26+
}
27+
28+
/**
29+
* Returns the lines where warnings should occur.
30+
*
31+
* @return array <int line number> => <int number of warnings>
32+
*/
33+
public function getWarningList() {
34+
$array = array_fill( 4, 33, 1 );
35+
$array[18] = 2;
36+
$array[21] = 2;
37+
return $array;
38+
39+
}
40+
41+
} // End class.

0 commit comments

Comments
 (0)