Add new methods for checking DUNDEFINED recursively, use them

The problem is explained at T250570#6068702; basically, the previous
check didn't account for DUNDEFINED nested deep inside arrays.

Bug: T250570
Change-Id: Iacee2db54ca00108de6339bb3dae70af7e2eeb56
This commit is contained in:
Daimona Eaytoy 2020-04-19 13:56:09 +02:00
parent 35294711bd
commit 1d6b9f6617
4 changed files with 110 additions and 6 deletions

View file

@ -376,6 +376,42 @@ class AFPData {
return new AFPData( $type, $res );
}
/**
* Check whether this instance contains the DUNDEFINED type, recursively
* @return bool
*/
public function hasUndefined() : bool {
if ( $this->type === self::DUNDEFINED ) {
return true;
}
if ( $this->type === self::DARRAY ) {
foreach ( $this->data as $el ) {
if ( $el->hasUndefined() ) {
return true;
}
}
}
return false;
}
/**
* Return a clone of this instance where DUNDEFINED is replaced with DNULL
* @return $this
*/
public function cloneAsUndefinedReplacedWithNull() : self {
if ( $this->type === self::DUNDEFINED ) {
return new self( self::DNULL );
}
if ( $this->type === self::DARRAY ) {
$data = [];
foreach ( $this->data as $el ) {
$data[] = $el->cloneAsUndefinedReplacedWithNull();
}
return new self( self::DARRAY, $data );
}
return clone $this;
}
/** Convert shorteners */
/**

View file

@ -1148,8 +1148,8 @@ class AbuseFilterParser extends AFPTransitionBase {
// @todo This is subpar.
$hasUndefinedArg = false;
foreach ( $args as $i => $arg ) {
if ( $arg->type === AFPData::DUNDEFINED ) {
$args[$i] = new AFPData( AFPData::DNULL );
if ( $arg->hasUndefined() ) {
$args[$i] = $arg->cloneAsUndefinedReplacedWithNull();
$hasUndefinedArg = true;
}
}
@ -1184,12 +1184,12 @@ class AbuseFilterParser extends AFPTransitionBase {
$this->raiseCondCount();
$hasUndefinedOperand = false;
if ( $lhs->getType() === AFPData::DUNDEFINED ) {
$lhs = new AFPData( AFPData::DNULL );
if ( $lhs->hasUndefined() ) {
$lhs = $lhs->cloneAsUndefinedReplacedWithNull();
$hasUndefinedOperand = true;
}
if ( $rhs->getType() === AFPData::DUNDEFINED ) {
$rhs = new AFPData( AFPData::DNULL );
if ( $rhs->hasUndefined() ) {
$rhs = $rhs->cloneAsUndefinedReplacedWithNull();
$hasUndefinedOperand = true;
}
if ( $hasUndefinedOperand ) {

View file

@ -256,4 +256,65 @@ class AFPDataTest extends AbuseFilterParserTestCase {
$this->expectExceptionMessage( 'Refusing to cast' );
AFPData::castTypes( $data, AFPData::DNULL );
}
/**
* @param AFPData $obj
* @param bool $expected
* @covers AFPData::hasUndefined
* @dataProvider provideHasUndefined
*/
public function testHasUndefined( AFPData $obj, bool $expected ) {
$this->assertSame( $expected, $obj->hasUndefined() );
}
/**
* Provider for testHasUndefined
*/
public function provideHasUndefined() {
return [
[ new AFPData( AFPData::DUNDEFINED ), true ],
[ new AFPData( AFPData::DNULL ), false ],
[ new AFPData( AFPData::DSTRING, '' ), false ],
[ new AFPData( AFPData::DARRAY, [ new AFPData( AFPData::DUNDEFINED ) ] ), true ],
[ new AFPData( AFPData::DARRAY, [ new AFPData( AFPData::DNULL ) ] ), false ],
];
}
/**
* @param AFPData $obj
* @param AFPData $expected
* @covers AFPData::cloneAsUndefinedReplacedWithNull
* @dataProvider provideCloneAsUndefinedReplacedWithNull
*/
public function testCloneAsUndefinedReplacedWithNull( AFPData $obj, AFPData $expected ) {
$this->assertEquals( $expected, $obj->cloneAsUndefinedReplacedWithNull() );
}
/**
* Provider for testHasUndefined
*/
public function provideCloneAsUndefinedReplacedWithNull() {
return [
[
new AFPData( AFPData::DUNDEFINED ),
new AFPData( AFPData::DNULL )
],
[
new AFPData( AFPData::DNULL ),
new AFPData( AFPData::DNULL )
],
[
new AFPData( AFPData::DSTRING, '' ),
new AFPData( AFPData::DSTRING, '' )
],
[
new AFPData( AFPData::DARRAY, [ new AFPData( AFPData::DUNDEFINED ) ] ),
new AFPData( AFPData::DARRAY, [ new AFPData( AFPData::DNULL ) ] )
],
[
new AFPData( AFPData::DARRAY, [ new AFPData( AFPData::DNULL ) ] ),
new AFPData( AFPData::DARRAY, [ new AFPData( AFPData::DNULL ) ] )
],
];
}
}

View file

@ -1009,6 +1009,12 @@ class AbuseFilterParserTest extends AbuseFilterParserTestCase {
[ "false & (var := [ 1,2,3 ]); var[] := 'baz'; 'baz' in var" ],
// But allow overwriting the whole variable
[ "false & (var := [ 1,2,3 ]); var := [4,5,6]; var !== [4,5,6]" ],
// Recursive DUNDEFINED replacement, T250570
[ '"x" in [ user_name ]' ],
[ 'string(user_name) in [ user_name ]' ],
[ 'string(user_name) in [ "x" ]' ],
[ '[ [ user_name ] ] in [ [ user_name ] ]' ],
[ 'equals_to_any( [ user_name ], [ user_name ] )' ],
];
}
@ -1197,6 +1203,7 @@ class AbuseFilterParserTest extends AbuseFilterParserTestCase {
return [
[ 'accountname rlike "("', 'regexfailure' ],
[ 'contains_any( new_wikitext, "foo", 3/0 )', 'dividebyzero' ],
[ 'contains_any( added_lines, [ user_name, [ 3/0 ] ] )', 'dividebyzero' ],
[ 'rcount( "(", added_lines )', 'regexfailure' ],
[ 'get_matches( "(", new_wikitext )', 'regexfailure' ],
[ 'added_lines contains string(3/0)', 'dividebyzero' ],