Initialize user-defined variables during shortcircuit

Bug: T214674
Depends-On: I5a14d4b2bc3ffd9caaaa095f16f36b9b6009db05
Change-Id: Id85c673337fa90a3782fd22eb9690cd996967111
This commit is contained in:
Daimona Eaytoy 2019-07-14 11:46:12 +02:00 committed by Huji
parent 1665e76109
commit 56e6117afd
3 changed files with 73 additions and 0 deletions

View file

@ -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 );
}
}
}
}

View file

@ -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, [] );
}

View file

@ -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" );
}
}
}
}