mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/AbuseFilter.git
synced 2024-11-13 17:27:20 +00:00
Initialize user-defined variables during shortcircuit
Bug: T214674 Depends-On: I5a14d4b2bc3ffd9caaaa095f16f36b9b6009db05 Change-Id: Id85c673337fa90a3782fd22eb9690cd996967111
This commit is contained in:
parent
1665e76109
commit
56e6117afd
|
@ -155,6 +155,11 @@ class AbuseFilterCachingParser extends AbuseFilterParser {
|
|||
list( $array, $offset ) = $node->children;
|
||||
|
||||
$array = $this->evalNode( $array );
|
||||
|
||||
if ( $array->getType() === AFPData::DNONE ) {
|
||||
return new AFPData( AFPData::DNONE );
|
||||
}
|
||||
|
||||
if ( $array->getType() !== AFPData::DARRAY ) {
|
||||
throw new AFPUserVisibleException( 'notarray', $node->position, [] );
|
||||
}
|
||||
|
@ -235,6 +240,9 @@ class AbuseFilterCachingParser extends AbuseFilterParser {
|
|||
$value = $leftOperand->toBool();
|
||||
// Short-circuit.
|
||||
if ( ( !$value && $op === '&' ) || ( $value && $op === '|' ) ) {
|
||||
if ( $rightOperand instanceof AFPTreeNode ) {
|
||||
$this->discardNode( $rightOperand );
|
||||
}
|
||||
return $leftOperand;
|
||||
}
|
||||
$rightOperand = $this->evalNode( $rightOperand );
|
||||
|
@ -305,4 +313,30 @@ class AbuseFilterCachingParser extends AbuseFilterParser {
|
|||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Intended to be used for short-circuit. Given a node, check it and its children; if there are
|
||||
* assignments, execute them. T214674
|
||||
*
|
||||
* @param AFPTreeNode $node
|
||||
*/
|
||||
private function discardNode( AFPTreeNode $node ) {
|
||||
if ( $node->type === AFPTreeNode::ASSIGNMENT ) {
|
||||
$this->setUserVariable( $node->children[0], new AFPData( AFPData::DNONE ) );
|
||||
} elseif ( $node->type === AFPTreeNode::INDEX_ASSIGNMENT ) {
|
||||
$varName = $node->children[0];
|
||||
if ( !$this->mVariables->varIsSet( $varName ) ) {
|
||||
throw new AFPUserVisibleException( 'unrecognisedvar', $node->position, [ $varName ] );
|
||||
}
|
||||
$this->setUserVariable( $varName, new AFPData( AFPData::DNONE ) );
|
||||
} elseif ( $node->type === AFPTreeNode::ATOM ) {
|
||||
return;
|
||||
}
|
||||
// @phan-suppress-next-line PhanTypeSuspiciousNonTraversableForeach ATOM case excluded above
|
||||
foreach ( $node->children as $child ) {
|
||||
if ( $child instanceof AFPTreeNode ) {
|
||||
$this->discardNode( $child );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -230,6 +230,21 @@ class AbuseFilterParser {
|
|||
} elseif ( $this->mCur->value === ')' ) {
|
||||
$braces--;
|
||||
}
|
||||
} elseif ( $this->mCur->type === AFPToken::TID ) {
|
||||
// T214674, define variables
|
||||
$varname = $this->mCur->value;
|
||||
$this->move();
|
||||
if ( $this->mCur->type === AFPToken::TOP && $this->mCur->value === ':=' ) {
|
||||
$this->setUserVariable( $varname, new AFPData( AFPData::DNONE ) );
|
||||
} elseif ( $this->mCur->type === AFPToken::TSQUAREBRACKET && $this->mCur->value === '[' ) {
|
||||
if ( !$this->mVariables->varIsSet( $varname ) ) {
|
||||
throw new AFPUserVisibleException( 'unrecognisedvar',
|
||||
$this->mCur->pos,
|
||||
[ $varname ]
|
||||
);
|
||||
}
|
||||
$this->setUserVariable( $varname, new AFPData( AFPData::DNONE ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( !( $this->mCur->type === AFPToken::TBRACE && $this->mCur->value === ')' ) ) {
|
||||
|
@ -710,6 +725,8 @@ class AbuseFilterParser {
|
|||
[ $idx, count( $result->getData() ) ] );
|
||||
}
|
||||
$result = $result->getData()[$idx];
|
||||
} elseif ( $result->getType() === AFPData::DNONE ) {
|
||||
$result = new AFPData( AFPData::DNONE );
|
||||
} else {
|
||||
throw new AFPUserVisibleException( 'notarray', $this->mCur->pos, [] );
|
||||
}
|
||||
|
|
|
@ -704,4 +704,26 @@ class AbuseFilterParserTest extends AbuseFilterParserTestCase {
|
|||
yield $case => [ $case, false ];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that code declaring a variable in a skipped brace (because of shortcircuit)
|
||||
* will be parsed without throwing an exception when later trying to use that var. T214674
|
||||
*/
|
||||
public function testVarDeclarationInSkippedBlock() {
|
||||
$code =
|
||||
"arr := [5]; false & (1 == 1; var := 'b'; arr[1] := 'x'; 3 < 4); var != 'b' & arr[1] != 'x'";
|
||||
|
||||
foreach ( self::getParsers() as $parser ) {
|
||||
$pname = get_class( $parser );
|
||||
try {
|
||||
// Whether this evaluating to true makes sense is another matter.
|
||||
$this->assertTrue(
|
||||
$parser->parse( $code ),
|
||||
"Parser: $pname"
|
||||
);
|
||||
} catch ( Exception $e ) {
|
||||
$this->fail( "Parser: $pname\n$e" );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue