-
Notifications
You must be signed in to change notification settings - Fork 43
Expand file tree
/
Copy pathLowExpiryCacheTimeSniff.php
More file actions
210 lines (180 loc) · 6.5 KB
/
LowExpiryCacheTimeSniff.php
File metadata and controls
210 lines (180 loc) · 6.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
<?php
/**
* WordPressVIPMinimum Coding Standard.
*
* @package VIPCS\WordPressVIPMinimum
* @link https://github.com/Automattic/VIP-Coding-Standards
* @license https://opensource.org/license/gpl-2-0 GPL-2.0
*/
namespace WordPressVIPMinimum\Sniffs\Performance;
use PHP_CodeSniffer\Util\Tokens;
use PHPCSUtils\Utils\Numbers;
use PHPCSUtils\Utils\PassedParameters;
use PHPCSUtils\Utils\TextStrings;
use WordPressCS\WordPress\AbstractFunctionParameterSniff;
/**
* This sniff throws a warning when low cache times are set.
*
* {@internal VIP uses the Memcached object cache implementation. {@link https://github.com/Automattic/wp-memcached}}
*
* @since 0.4.0
*/
class LowExpiryCacheTimeSniff extends AbstractFunctionParameterSniff {
/**
* The group name for this group of functions.
*
* @var string
*/
protected $group_name = 'cache_functions';
/**
* Functions this sniff is looking for.
*
* @var array<string, bool> Key is the function name, value irrelevant.
*/
protected $target_functions = [
'wp_cache_set' => true,
'wp_cache_add' => true,
'wp_cache_replace' => true,
];
/**
* List of WP time constants, see https://codex.wordpress.org/Easier_Expression_of_Time_Constants.
*
* @var array<string, int>
*/
protected $wp_time_constants = [
'MINUTE_IN_SECONDS' => 60,
'HOUR_IN_SECONDS' => 3600,
'DAY_IN_SECONDS' => 86400,
'WEEK_IN_SECONDS' => 604800,
'MONTH_IN_SECONDS' => 2592000,
'YEAR_IN_SECONDS' => 31536000,
];
/**
* Process the parameters of a matched function.
*
* @param int $stackPtr The position of the current token in the stack.
* @param string $group_name The name of the group which was matched.
* @param string $matched_content The token content (function name) which was matched
* in lowercase.
* @param array $parameters Array with information about the parameters.
*
* @return int|void Integer stack pointer to skip forward or void to continue
* normal file processing.
*/
public function process_parameters( $stackPtr, $group_name, $matched_content, $parameters ) {
$expire_param = PassedParameters::getParameterFromStack( $parameters, 4, 'expire' );
if ( $expire_param === false ) {
// If no cache expiry time, bail (i.e. we don't want to flag for something like feeds where it is cached indefinitely until a hook runs).
return;
}
$tokensAsString = '';
$reportPtr = null;
$openParens = 0;
$message = 'Cache expiry time could not be determined. Please inspect that the fourth parameter passed to %s() evaluates to 300 seconds or more. Found: "%s"';
$error_code = 'CacheTimeUndetermined';
$data = [ $matched_content, $expire_param['clean'] ];
for ( $i = $expire_param['start']; $i <= $expire_param['end']; $i++ ) {
if ( isset( Tokens::$emptyTokens[ $this->tokens[ $i ]['code'] ] ) === true ) {
$tokensAsString .= ' ';
continue;
}
if ( $this->tokens[ $i ]['code'] === T_NS_SEPARATOR ) {
/*
* Ignore namespace separators. If it's part of a global WP time constant, it will be
* handled correctly. If it's used in any other context, another token *will* trigger the
* "undetermined" warning anyway.
*/
continue;
}
if ( isset( $reportPtr ) === false ) {
// Set the report pointer to the first non-empty token we encounter.
$reportPtr = $i;
}
if ( $this->tokens[ $i ]['code'] === T_LNUMBER
|| $this->tokens[ $i ]['code'] === T_DNUMBER
) {
// Make sure that PHP 7.4 numeric literals and PHP 8.1 explicit octals don't cause problems.
$number_info = Numbers::getCompleteNumber( $this->phpcsFile, $i );
$tokensAsString .= $number_info['decimal'];
$i = $number_info['last_token'];
continue;
}
if ( $this->tokens[ $i ]['code'] === T_FALSE
|| $this->tokens[ $i ]['code'] === T_NULL
) {
$tokensAsString .= 0;
continue;
}
if ( $this->tokens[ $i ]['code'] === T_TRUE ) {
$tokensAsString .= 1;
continue;
}
if ( isset( Tokens::$arithmeticTokens[ $this->tokens[ $i ]['code'] ] ) === true ) {
$tokensAsString .= $this->tokens[ $i ]['content'];
continue;
}
// If using time constants, we need to convert to a number.
if ( $this->tokens[ $i ]['code'] === T_STRING
&& isset( $this->wp_time_constants[ $this->tokens[ $i ]['content'] ] ) === true
) {
$tokensAsString .= $this->wp_time_constants[ $this->tokens[ $i ]['content'] ];
continue;
}
if ( $this->tokens[ $i ]['code'] === T_OPEN_PARENTHESIS ) {
$tokensAsString .= $this->tokens[ $i ]['content'];
++$openParens;
continue;
}
if ( $this->tokens[ $i ]['code'] === T_CLOSE_PARENTHESIS ) {
$tokensAsString .= $this->tokens[ $i ]['content'];
--$openParens;
continue;
}
if ( $this->tokens[ $i ]['code'] === T_CONSTANT_ENCAPSED_STRING ) {
$content = TextStrings::stripQuotes( $this->tokens[ $i ]['content'] );
if ( is_numeric( $content ) === true ) {
$tokensAsString .= $content;
continue;
}
}
// Encountered an unexpected token. Manual inspection needed.
$this->phpcsFile->addWarning( $message, $reportPtr, $error_code, $data );
return;
}
if ( $tokensAsString === '' ) {
// Nothing found to evaluate.
return;
}
$tokensAsString = trim( $tokensAsString );
if ( $openParens !== 0 ) {
/*
* Shouldn't be possible as that would indicate a parse error in the original code,
* but let's prevent getting parse errors in the `eval`-ed code.
*/
if ( $openParens > 0 ) {
$tokensAsString .= str_repeat( ')', $openParens );
} else {
$tokensAsString = str_repeat( '(', abs( $openParens ) ) . $tokensAsString;
}
}
$time = @eval( "return $tokensAsString;" ); // phpcs:ignore Squiz.PHP.Eval,WordPress.PHP.NoSilencedErrors -- No harm here.
if ( $time === false ) {
/*
* The eval resulted in a parse error. This will only happen for backfilled
* arithmetic operator tokens, like T_POW, on PHP versions in which the token
* did not exist. In that case, flag for manual inspection.
*/
$this->phpcsFile->addWarning( $message, $reportPtr, $error_code, $data );
return;
}
if ( $time < 300 && (int) $time !== 0 ) {
$message = 'Low cache expiry time of %s seconds detected. It is recommended to have 300 seconds or more.';
$data = [ $time ];
if ( (string) $time !== $tokensAsString ) {
$message .= ' Found: "%s"';
$data[] = $tokensAsString;
}
$this->phpcsFile->addWarning( $message, $reportPtr, 'LowCacheTime', $data );
}
}
}