Skip to content

Commit afcb17e

Browse files
rodrigoprimojrfnl
andauthored
Security/EscapeOutput: fix false negatives when handling anonymous classes (#2559)
* Security/EscapeOutput: fix false negatives when handling anonymous classes This commit fixes false negatives when the sniff handles readonly anonymous classes and anonymous classes with attributes that are part of a throw statement. When stepping over tokens after `T_THROW` to find the `T_OPEN_PARENTHESIS` of the exception creation function call/class instantiation, the sniff was not considering that it might need to step over `T_READONLY` tokens or attribute declarations when dealing with anonymous classes. Fixes #2552 --------- Co-authored-by: jrfnl <[email protected]>
1 parent fa2b44e commit afcb17e

File tree

7 files changed

+68
-12
lines changed

7 files changed

+68
-12
lines changed

.phpcs.xml.dist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
<exclude name="PHPCompatibility.Constants.NewConstants.t_coalesceFound"/>
6969
<exclude name="PHPCompatibility.Constants.NewConstants.t_coalesce_equalFound"/>
7070
<exclude name="PHPCompatibility.Constants.NewConstants.t_yield_fromFound"/>
71+
<exclude name="PHPCompatibility.Constants.NewConstants.t_readonlyFound"/>
7172
</rule>
7273

7374
<!-- Enforce PSR1 compatible namespaces. -->

WordPress/Sniffs/Security/EscapeOutputSniff.php

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -216,22 +216,35 @@ public function process_token( $stackPtr ) {
216216

217217
case \T_THROW:
218218
// Find the open parentheses, while stepping over the exception creation tokens.
219-
$ignore = Tokens::$emptyTokens;
220-
$ignore += Collections::namespacedNameTokens();
221-
$ignore += Collections::functionCallTokens();
222-
$ignore += Collections::objectOperators();
223-
224-
$next_relevant = $this->phpcsFile->findNext( $ignore, ( $stackPtr + 1 ), null, true );
225-
if ( false === $next_relevant ) {
226-
return;
227-
}
228-
229-
if ( \T_NEW === $this->tokens[ $next_relevant ]['code'] ) {
219+
$ignore = Tokens::$emptyTokens;
220+
$ignore += Collections::namespacedNameTokens();
221+
$ignore += Collections::functionCallTokens();
222+
$ignore += Collections::objectOperators();
223+
$ignore[ \T_READONLY ] = \T_READONLY;
224+
225+
$next_relevant = $stackPtr;
226+
do {
230227
$next_relevant = $this->phpcsFile->findNext( $ignore, ( $next_relevant + 1 ), null, true );
231228
if ( false === $next_relevant ) {
232229
return;
233230
}
234-
}
231+
232+
if ( \T_NEW === $this->tokens[ $next_relevant ]['code'] ) {
233+
continue;
234+
}
235+
236+
// Skip over attribute declarations when searching for the open parenthesis.
237+
if ( \T_ATTRIBUTE === $this->tokens[ $next_relevant ]['code'] ) {
238+
if ( isset( $this->tokens[ $next_relevant ]['attribute_closer'] ) === false ) {
239+
return;
240+
}
241+
242+
$next_relevant = $this->tokens[ $next_relevant ]['attribute_closer'];
243+
continue;
244+
}
245+
246+
break;
247+
} while ( $next_relevant < ( $this->phpcsFile->numTokens - 1 ) );
235248

236249
if ( \T_OPEN_PARENTHESIS !== $this->tokens[ $next_relevant ]['code']
237250
|| isset( $this->tokens[ $next_relevant ]['parenthesis_closer'] ) === false

WordPress/Tests/Security/EscapeOutputUnitTest.1.inc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,3 +662,17 @@ die( status: esc_html( $foo ) ); // Ok.
662662

663663
exit( status: $foo ); // Bad.
664664
die( status: $foo ); // Bad.
665+
666+
/*
667+
* Issue https://github.com/WordPress/WordPress-Coding-Standards/issues/2552
668+
* Ensure that readonly anonymous classes and anonymous classes with attributes are handled
669+
* correctly when part of a throw statement.
670+
*/
671+
throw new #[MyAttribute] readonly class( esc_html( $message ) ) extends Exception {}; // Good.
672+
throw new readonly class( $unescaped ) {}; // Bad.
673+
throw new #[MyAttribute] class( $unescaped ) extends Exception {}; // Bad.
674+
throw new
675+
#[Attribute1]
676+
/* some comment */
677+
#[Attribute2('text', 10)]
678+
readonly class( $unescaped ) {}; // Bad.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
/*
4+
* Intentional parse error (nothing after T_ATTRIBUTE).
5+
* This should be the only test in this file.
6+
*/
7+
8+
throw new #[
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
/*
4+
* Intentional parse error (only whitespaces after T_ATTRIBUTE_END).
5+
* This should be the only test in this file.
6+
*/
7+
8+
throw new #[MyAttribute]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
/*
4+
* Intentional parse error (nothing after T_ATTRIBUTE_END).
5+
* There should be no whitespaces at the end of this file.
6+
* This should be the only test in this file.
7+
*/
8+
9+
throw new #[MyAttribute]

WordPress/Tests/Security/EscapeOutputUnitTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,9 @@ public function getErrorList( $testFile = '' ) {
161161
657 => 1,
162162
663 => 1,
163163
664 => 1,
164+
672 => 1,
165+
673 => 1,
166+
678 => 1,
164167
);
165168

166169
case 'EscapeOutputUnitTest.6.inc':

0 commit comments

Comments
 (0)