Skip to content

Commit 8e07f73

Browse files
committed
Merge pull request #376 from WordPress-Coding-Standards/issue/186
Santized sniff: don't flag comparisons
2 parents b814c05 + 0326499 commit 8e07f73

File tree

4 files changed

+257
-162
lines changed

4 files changed

+257
-162
lines changed

WordPress/Sniff.php

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,218 @@ protected function is_sanitized( $stackPtr ) {
446446
// Check if this is a sanitizing function.
447447
return in_array( $functionName, WordPress_Sniffs_XSS_EscapeOutputSniff::$sanitizingFunctions );
448448
}
449+
450+
/**
451+
* Get the index key of an array variable.
452+
*
453+
* E.g., "bar" in $foo['bar'].
454+
*
455+
* @since 0.5.0
456+
*
457+
* @param int $stackPtr The index of the token in the stack.
458+
*
459+
* @return string|false The array index key whose value is being accessed.
460+
*/
461+
protected function get_array_access_key( $stackPtr ) {
462+
463+
// Find the next non-empty token.
464+
$open_bracket = $this->phpcsFile->findNext(
465+
PHP_CodeSniffer_Tokens::$emptyTokens,
466+
$stackPtr + 1,
467+
null,
468+
true
469+
);
470+
471+
// If it isn't a bracket, this isn't an array-access.
472+
if ( T_OPEN_SQUARE_BRACKET !== $this->tokens[ $open_bracket ]['code'] ) {
473+
return false;
474+
}
475+
476+
$key = $this->phpcsFile->getTokensAsString(
477+
$open_bracket + 1
478+
, $this->tokens[ $open_bracket ]['bracket_closer'] - $open_bracket - 1
479+
);
480+
481+
return trim( $key );
482+
}
483+
484+
/**
485+
* Check if the existence of a variable is validated with isset() or empty().
486+
*
487+
* When $in_condition_only is false, (which is the default), this is considered
488+
* valid:
489+
*
490+
* ```php
491+
* if ( isset( $var ) ) {
492+
* // Do stuff, like maybe return or exit (but could be anything)
493+
* }
494+
*
495+
* foo( $var );
496+
* ```
497+
*
498+
* When it is true, that would be invalid, the use of the variable must be within
499+
* the scope of the validating condition, like this:
500+
*
501+
* ```php
502+
* if ( isset( $var ) ) {
503+
* foo( $var );
504+
* }
505+
* ```
506+
*
507+
* @since 0.5.0
508+
*
509+
* @param int $stackPtr The index of this token in the stack.
510+
* @param string $array_key An array key to check for ("bar" in $foo['bar']).
511+
* @param bool $in_condition_only Whether to require that this use of the
512+
* variable occur within the scope of the
513+
* validating condition, or just in the same
514+
* scope as it (default).
515+
*
516+
* @return bool Whether the var is validated.
517+
*/
518+
protected function is_validated( $stackPtr, $array_key = null, $in_condition_only = false ) {
519+
520+
if ( $in_condition_only ) {
521+
522+
// This is a stricter check, requiring the variable to be used only
523+
// within the validation condition.
524+
525+
// If there are no conditions, there's no validation.
526+
if ( empty( $this->tokens[ $stackPtr ]['conditions'] ) ) {
527+
return false;
528+
}
529+
530+
$conditions = $this->tokens[ $stackPtr ]['conditions'];
531+
end( $conditions ); // Get closest condition
532+
$conditionPtr = key( $conditions );
533+
$condition = $this->tokens[ $conditionPtr ];
534+
535+
if ( ! isset( $condition['parenthesis_opener'] ) ) {
536+
537+
$this->phpcsFile->addError(
538+
'Possible parse error, condition missing open parenthesis.',
539+
$conditionPtr,
540+
'IsValidatedMissingConditionOpener'
541+
);
542+
543+
return false;
544+
}
545+
546+
$scope_start = $condition['parenthesis_opener'];
547+
$scope_end = $condition['parenthesis_closer'];
548+
549+
} else {
550+
551+
// We are are more loose, requiring only that the variable be validated
552+
// in the same function/file scope as it is used.
553+
554+
// Check if we are in a function.
555+
$function = $this->phpcsFile->findPrevious( T_FUNCTION, $stackPtr );
556+
557+
// If so, we check only within the function, otherwise the whole file.
558+
if ( false !== $function && $stackPtr < $this->tokens[ $function ]['scope_closer'] ) {
559+
$scope_start = $this->tokens[ $function ]['scope_opener'];
560+
} else {
561+
$scope_start = 0;
562+
}
563+
564+
$scope_end = $stackPtr;
565+
}
566+
567+
for ( $i = $scope_start + 1; $i < $scope_end; $i++ ) {
568+
569+
if ( ! in_array( $this->tokens[ $i ]['code'], array( T_ISSET, T_EMPTY, T_UNSET ) ) ) {
570+
continue;
571+
}
572+
573+
$issetOpener = $this->phpcsFile->findNext( T_OPEN_PARENTHESIS, $i );
574+
$issetCloser = $this->tokens[ $issetOpener ]['parenthesis_closer'];
575+
576+
// Look for this variable. We purposely stomp $i from the parent loop.
577+
for ( $i = $issetOpener + 1; $i < $issetCloser; $i++ ) {
578+
579+
if ( T_VARIABLE !== $this->tokens[ $i ]['code'] ) {
580+
continue;
581+
}
582+
583+
// If we're checking for a specific array key (ex: 'hello' in
584+
// $_POST['hello']), that mush match too.
585+
if ( $array_key && $this->get_array_access_key( $i ) !== $array_key ) {
586+
continue;
587+
}
588+
589+
return true;
590+
}
591+
}
592+
593+
return false;
594+
}
595+
596+
/**
597+
* Check whether a variable is being compared to another value.
598+
*
599+
* E.g., $var === 'foo', 1 <= $var, etc.
600+
*
601+
* Also recognizes `switch ( $var )`.
602+
*
603+
* @since 0.5.0
604+
*
605+
* @param int $stackPtr The index of this token in the stack.
606+
*
607+
* @return bool Whether this is a comparison.
608+
*/
609+
protected function is_comparison( $stackPtr ) {
610+
611+
// We first check if this is a switch statement (switch ( $var )).
612+
if ( isset( $this->tokens[ $stackPtr ]['nested_parenthesis'] ) ) {
613+
$close_parenthesis = end( $this->tokens[ $stackPtr ]['nested_parenthesis'] );
614+
615+
if (
616+
isset( $this->tokens[ $close_parenthesis ]['parenthesis_owner'] )
617+
&& T_SWITCH === $this->tokens[ $this->tokens[ $close_parenthesis ]['parenthesis_owner'] ]['code']
618+
) {
619+
return true;
620+
}
621+
}
622+
623+
// Find the previous non-empty token. We check before the var first because
624+
// yoda conditions are usually expected.
625+
$previous_token = $this->phpcsFile->findPrevious(
626+
PHP_CodeSniffer_Tokens::$emptyTokens,
627+
$stackPtr - 1,
628+
null,
629+
true
630+
);
631+
632+
if ( in_array( $this->tokens[ $previous_token ]['code'], PHP_CodeSniffer_Tokens::$comparisonTokens ) ) {
633+
return true;
634+
}
635+
636+
// Maybe the comparison operator is after this.
637+
$next_token = $this->phpcsFile->findNext(
638+
PHP_CodeSniffer_Tokens::$emptyTokens,
639+
$stackPtr + 1,
640+
null,
641+
true
642+
);
643+
644+
// This might be an opening square bracket in the case of arrays ($var['a']).
645+
while ( T_OPEN_SQUARE_BRACKET === $this->tokens[ $next_token ]['code'] ) {
646+
647+
$next_token = $this->phpcsFile->findNext(
648+
PHP_CodeSniffer_Tokens::$emptyTokens,
649+
$this->tokens[ $next_token ]['bracket_closer'] + 1,
650+
null,
651+
true
652+
);
653+
}
654+
655+
if ( in_array( $this->tokens[ $next_token ]['code'], PHP_CodeSniffer_Tokens::$comparisonTokens ) ) {
656+
return true;
657+
}
658+
659+
return false;
660+
}
449661
}
450662

451663
// EOF

0 commit comments

Comments
 (0)