jenkins-bot has submitted this change and it was merged.
Change subject: Clean-up of AbuseFilterParser::nextToken()
......................................................................
Clean-up of AbuseFilterParser::nextToken()
No functional changes.
* Don't include $code as part of the return value; it is ignored anyway.
* Removed AbuseFilterParser::lastHandledToken and AFPParserState::lastInput,
because AbuseFilterParser::nextToken() no longer calls itself recursively.
* The regular expression that matches operators is no longer constructed
dynamically, but hard-coded into the class. To make sure it does not drift
apart from the more legible AbuseFilterParser::$mOps, add a unit test that
constructs the regex dynamically as before and compares it to
AbuseFilterParser::OPERATOR_RE.
* AbuseFilterParser::RADIX_RE ditto.
Change-Id: I9c23b60759ed2f4c73a9b480243b16bbce5a208f
(cherry picked from commit b388dfab1be66ea5411ff3eeb5a35be24c35f01c)
(cherry picked from commit 11f17d0769dc4eca8e8908e43a1a97fba8fa3d8d)
---
M AbuseFilter.parser.php
M tests/phpunit/parserTest.php
2 files changed, 151 insertions(+), 178 deletions(-)
Approvals:
Ori.livneh: Looks good to me, approved
jenkins-bot: Verified
diff --git a/AbuseFilter.parser.php b/AbuseFilter.parser.php
index 82a9ab7..75cb508 100644
--- a/AbuseFilter.parser.php
+++ b/AbuseFilter.parser.php
@@ -513,12 +513,11 @@
}
class AFPParserState {
- public $pos, $token, $lastInput;
+ public $pos, $token;
public function __construct( $token, $pos ) {
$this->token = $token;
$this->pos = $pos;
- $this->lastInput = AbuseFilterParser::$lastHandledToken;
}
}
@@ -592,6 +591,21 @@
class AbuseFilterParser {
public $mParams, $mCode, $mTokens, $mPos, $mCur, $mShortCircuit,
$mAllowShort, $mLen;
+ const COMMENT_START_RE = '/\s*\/\*/A';
+ const ID_SYMBOL_RE = '/[0-9A-Za-z_]+/A';
+ const OPERATOR_RE =
'/(\!\=\=|\!\=|\!|\*\*|\*|\/|\+|\-|%|&|\||\^|\:\=|\?|\:|\<\=|\<|\>\=|\>|\=\=\=|\=\=|\=)/A';
+ const RADIX_RE = '/([0-9A-Fa-f]+(?:\.\d*)?|\.\d+)([bxo])?/Au';
+ const WHITESPACE = "\011\012\013\014\015\040";
+
+ static $mPunctuation = array(
+ ',' => AFPToken::TComma,
+ '(' => AFPToken::TBrace,
+ ')' => AFPToken::TBrace,
+ '[' => AFPToken::TSquareBracket,
+ ']' => AFPToken::TSquareBracket,
+ ';' => AFPToken::TStatementSeparator,
+ );
+
/**
* @var AbuseFilterVariableHolder
*/
@@ -645,16 +659,25 @@
'===', '==', '=', // Equality
);
+ static $mBases = array(
+ 'b' => 2,
+ 'x' => 16,
+ 'o' => 8
+ );
+
+ static $mBaseCharsRegEx = array(
+ 2 => '/^[01]+$/',
+ 8 => '/^[0-8]+$/',
+ 16 => '/^[0-9A-Fa-f]+$/',
+ 10 => '/^[0-9.]+$/',
+ );
+
static $mKeywords = array(
'in', 'like', 'true', 'false', 'null', 'contains', 'matches',
'rlike', 'irlike', 'regex', 'if', 'then', 'else', 'end',
);
- static $parserCache = array();
-
static $funcCache = array();
-
- static $lastHandledToken = array();
/**
* Create a new instance
@@ -720,7 +743,7 @@
* @return AFPToken
*/
protected function move() {
- list( $val, $type, , $offset ) = self::nextToken( $this->mCode,
$this->mPos );
+ list( $val, $type, $offset ) = self::nextToken( $this->mCode,
$this->mPos );
$token = new AFPToken( $type, $val, $this->mPos );
$this->mPos = $offset;
@@ -743,7 +766,6 @@
protected function setState( AFPParserState $state ) {
$this->mCur = $state->token;
$this->mPos = $state->pos;
- self::$lastHandledToken = $state->lastInput;
}
/**
@@ -1428,211 +1450,141 @@
* @throws AFPException
* @throws AFPUserVisibleException
*/
- static function nextToken( $code, $offset ) {
- $tok = '';
+ static function readStringLiteral( $code, $offset ) {
+ $type = $code[$offset];
+ $offset++;
+ $length = strlen( $code );
+ $token = '';
+ while ( $offset < $length ) {
+ if ( $code[$offset] === $type ) {
+ $offset++;
+ return array( $token, AFPToken::TString,
$offset );
+ }
- // Check for infinite loops
- if ( self::$lastHandledToken == array( $code, $offset ) ) {
- // Should never happen
- throw new AFPException( "Entered infinite loop. Offset
$offset of $code" );
+ // Performance: Use a PHP function (implemented in C)
+ // to scan ahead.
+ $addLength = strcspn( $code, $type . "\\", $offset );
+ if ( $addLength ) {
+ $token .= substr( $code, $offset, $addLength );
+ $offset += $addLength;
+ } elseif ( $code[$offset] == '\\' ) {
+ switch( $code[$offset + 1] ) {
+ case '\\':
+ $token .= '\\';
+ break;
+ case $type:
+ $token .= $type;
+ break;
+ case 'n';
+ $token .= "\n";
+ break;
+ case 'r':
+ $token .= "\r";
+ break;
+ case 't':
+ $token .= "\t";
+ break;
+ case 'x':
+ $chr = substr( $code, $offset +
2, 2 );
+
+ if ( preg_match(
'/^[0-9A-Fa-f]{2}$/', $chr ) ) {
+ $chr = base_convert(
$chr, 16, 10 );
+ $token .= chr( $chr );
+ $offset += 2; # \xXX --
2 done later
+ } else {
+ $token .= 'x';
+ }
+ break;
+ default:
+ $token .= "\\" . $code[$offset
+ 1];
+ }
+
+ $offset += 2;
+
+ } else {
+ $token .= $code[$offset];
+ $offset++;
+ }
}
+ throw new AFPUserVisibleException( 'unclosedstring', $offset,
array() );
+ }
- self::$lastHandledToken = array( $code, $offset );
+ /**
+ * @param $code
+ * @param $offset
+ * @return array
+ * @throws AFPException
+ * @throws AFPUserVisibleException
+ */
+ static function nextToken( $code, $offset ) {
+ $matches = array();
+
+ // Read past comments
+ while ( preg_match( '/\s*\/\*/A', $code, $matches, 0, $offset )
) {
+ $offset = strpos( $code, '*/', $offset ) + 2;
+ }
// Spaces
- $matches = array();
- if ( preg_match( '/\s+/uA', $code, $matches, 0, $offset ) ) {
- $offset += strlen( $matches[0] );
- }
-
+ $offset += strspn( $code, self::WHITESPACE, $offset );
if ( $offset >= strlen( $code ) ) {
- return array( '', AFPToken::TNone, $code, $offset );
+ return array( '', AFPToken::TNone, $offset );
}
- // Comments
- if ( substr( $code, $offset, 2 ) == '/*' ) {
- $end = strpos( $code, '*/', $offset );
+ $chr = $code[$offset];
- return self::nextToken( $code, $end + 2 );
+ // Punctuation
+ if ( isset( self::$mPunctuation[$chr] ) ) {
+ return array( $chr, self::$mPunctuation[$chr], $offset
+ 1 );
}
- // Commas
- if ( $code[$offset] == ',' ) {
- return array( ',', AFPToken::TComma, $code, $offset + 1
);
- }
-
- // Braces
- if ( $code[$offset] == '(' or $code[$offset] == ')' ) {
- return array( $code[$offset], AFPToken::TBrace, $code,
$offset + 1 );
- }
-
- // Square brackets
- if ( $code[$offset] == '[' or $code[$offset] == ']' ) {
- return array( $code[$offset], AFPToken::TSquareBracket,
$code, $offset + 1 );
- }
-
- // Semicolons
- if ( $code[$offset] == ';' ) {
- return array( ';', AFPToken::TStatementSeparator,
$code, $offset + 1 );
- }
-
- // Strings
- if ( $code[$offset] == '"' || $code[$offset] == "'" ) {
- $type = $code[$offset];
- $offset++;
- $strLen = strlen( $code );
- while ( $offset < $strLen ) {
-
- if ( $code[$offset] == $type ) {
- $offset++;
- return array( $tok, AFPToken::TString,
$code, $offset );
- }
-
- // Performance: Use a PHP function (implemented
in C)
- // to scan ahead.
- $addLength = strcspn( $code, $type . "\\",
$offset );
- if ( $addLength ) {
- $tok .= substr( $code, $offset,
$addLength );
- $offset += $addLength;
- } elseif ( $code[$offset] == '\\' ) {
- switch( $code[$offset + 1] ) {
- case '\\':
- $tok .= '\\';
- break;
- case $type:
- $tok .= $type;
- break;
- case 'n';
- $tok .= "\n";
- break;
- case 'r':
- $tok .= "\r";
- break;
- case 't':
- $tok .= "\t";
- break;
- case 'x':
- $chr = substr( $code,
$offset + 2, 2 );
-
- if ( preg_match(
'/^[0-9A-Fa-f]{2}$/', $chr ) ) {
- $chr =
base_convert( $chr, 16, 10 );
- $tok .= chr(
$chr );
- $offset += 2; #
\xXX -- 2 done later
- } else {
- $tok .= 'x';
- }
- break;
- default:
- $tok .= "\\" .
$code[$offset + 1];
- }
-
- $offset += 2;
-
- } else {
- $tok .= $code[$offset];
- $offset++;
- }
- }
- throw new AFPUserVisibleException( 'unclosedstring',
$offset, array() );
- }
-
- // Find operators
-
- static $operator_regex = null;
- // Match using a regex. Regexes are faster than PHP
- if ( !$operator_regex ) {
- $quoted_operators = array();
-
- foreach ( self::$mOps as $op ) {
- $quoted_operators[] = preg_quote( $op, '/' );
- }
- $operator_regex = '/(' . implode( '|',
$quoted_operators ) . ')/A';
+ // String literal
+ if ( $chr === '"' || $chr === "'" ) {
+ return self::readStringLiteral( $code, $offset );
}
$matches = array();
- preg_match( $operator_regex, $code, $matches, 0, $offset );
-
- if ( count( $matches ) ) {
- $tok = $matches[0];
- $offset += strlen( $tok );
- return array( $tok, AFPToken::TOp, $code, $offset );
+ // Operators
+ if ( preg_match( self::OPERATOR_RE, $code, $matches, 0, $offset
) ) {
+ $token = $matches[0];
+ return array( $token, AFPToken::TOp, $offset + strlen(
$token ) );
}
- // Find bare numbers
- $bases = array(
- 'b' => 2,
- 'x' => 16,
- 'o' => 8
- );
- $baseChars = array(
- 2 => '[01]',
- 16 => '[0-9A-Fa-f]',
- 8 => '[0-8]',
- 10 => '[0-9.]',
- );
- $baseClass = '[' . implode( '', array_keys( $bases ) ) . ']';
- $radixRegex = "/([0-9A-Fa-f]+(?:\.\d*)?|\.\d+)($baseClass)?/Au";
- $matches = array();
-
- if ( preg_match( $radixRegex, $code, $matches, 0, $offset ) ) {
+ // Numbers
+ if ( preg_match( self::RADIX_RE, $code, $matches, 0, $offset )
) {
+ $token = $matches[0];
$input = $matches[1];
$baseChar = @$matches[2];
// Sometimes the base char gets mixed in with the rest
of it because
// the regex targets hex, too.
// This mostly happens with binary
- if ( !$baseChar && !empty( $bases[ substr( $input, - 1
) ] ) ) {
+ if ( !$baseChar && !empty( self::$mBases[ substr(
$input, - 1 ) ] ) ) {
$baseChar = substr( $input, - 1, 1 );
$input = substr( $input, 0, - 1 );
}
- if ( $baseChar ) {
- $base = $bases[$baseChar];
- } else {
- $base = 10;
- }
+ $base = $baseChar ? self::$mBases[$baseChar] : 10;
// Check against the appropriate character class for
input validation
- $baseRegex = "/^" . $baseChars[$base] . "+$/";
- if ( preg_match( $baseRegex, $input ) ) {
- if ( $base != 10 ) {
- $num = base_convert( $input, $base, 10
);
- } else {
- $num = $input;
- }
-
- $offset += strlen( $matches[0] );
-
- $float = strpos( $input, '.' ) !== false;
-
- return array(
- $float
- ? floatval( $num )
- : intval( $num ),
- $float
- ? AFPToken::TFloat
- : AFPToken::TInt,
- $code,
- $offset
- );
+ if ( preg_match( self::$mBaseCharsRegEx[$base], $input
) ) {
+ $num = $base !== 10 ? base_convert( $input,
$base, 10 ) : $input;
+ $offset += strlen( $token );
+ return ( strpos( $input, '.' ) !== false )
+ ? array( floatval( $num ),
AFPToken::TFloat, $offset )
+ : array( intval( $num ),
AFPToken::TInt, $offset );
}
}
- // The rest are considered IDs
+ // IDs / Keywords
- // Regex match > PHP
- $idSymbolRegex = '/[0-9A-Za-z_]+/A';
- $matches = array();
-
- if ( preg_match( $idSymbolRegex, $code, $matches, 0, $offset )
) {
- $tok = $matches[0];
-
- $type = in_array( $tok, self::$mKeywords )
+ if ( preg_match( self::ID_SYMBOL_RE, $code, $matches, 0,
$offset ) ) {
+ $token = $matches[0];
+ $offset += strlen( $token );
+ $type = in_array( $token, self::$mKeywords )
? AFPToken::TKeyword
: AFPToken::TID;
-
- return array( $tok, $type, $code, $offset + strlen(
$tok ) );
+ return array( $token, $type, $offset );
}
throw new AFPUserVisibleException(
diff --git a/tests/phpunit/parserTest.php b/tests/phpunit/parserTest.php
index 0160462..20f1767 100644
--- a/tests/phpunit/parserTest.php
+++ b/tests/phpunit/parserTest.php
@@ -75,4 +75,25 @@
return $tests;
}
+
+ /**
+ * Ensure that AbsueFilterParser::OPERATOR_RE matches the contents
+ * and order of AbuseFilterParser::$mOps.
+ */
+ public function testOperatorRe() {
+ $operatorRe = '/(' . implode( '|', array_map( function ( $op ) {
+ return preg_quote( $op, '/' );
+ }, AbuseFilterParser::$mOps ) ) . ')/A';
+ $this->assertEquals( $operatorRe,
AbuseFilterParser::OPERATOR_RE );
+ }
+
+ /**
+ * Ensure that AbsueFilterParser::RADIX_RE matches the contents
+ * and order of AbuseFilterParser::$mBases.
+ */
+ public function testRadixRe() {
+ $baseClass = implode( '', array_keys(
AbuseFilterParser::$mBases ) );
+ $radixRe = "/([0-9A-Fa-f]+(?:\.\d*)?|\.\d+)([$baseClass])?/Au";
+ $this->assertEquals( $radixRe, AbuseFilterParser::RADIX_RE );
+ }
}
--
To view, visit https://gerrit.wikimedia.org/r/233846
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I9c23b60759ed2f4c73a9b480243b16bbce5a208f
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/AbuseFilter
Gerrit-Branch: wmf/1.26wmf19
Gerrit-Owner: Ori.livneh <[email protected]>
Gerrit-Reviewer: Ori.livneh <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits