Avoid implicit casts from DUNDEFINED to something else

This patch keeps the current behaviour for everything (since DUNDEFINED
was always casted to boolean false), but handles the cast at a higher
level instead of relying on what AFPData::castTypes will do. This way
it's easier to spot places where we may get DUNDEFINED, and decide how
to handle them one by one.

Change-Id: I1070e15ea03c7dd4a4231b87afbc42240a558581
This commit is contained in:
Daimona Eaytoy 2019-08-12 11:18:15 +02:00
parent 3748c41e79
commit 1fe3647268
3 changed files with 23 additions and 7 deletions

View file

@ -58,7 +58,7 @@ class AFPData {
*/
public function __construct( $type, $val = null ) {
if ( ( $type === self::DUNDEFINED || $type === self::DEMPTY ) && $val !== null ) {
// Sanity, and we rely on this for instance when casting a DUNDEFINED to something else
// Sanity
throw new InvalidArgumentException( 'DUNDEFINED cannot have a non-null value' );
}
$this->type = $type;
@ -111,6 +111,11 @@ class AFPData {
if ( $orig->type === $target ) {
return $orig->dup();
}
if ( $orig->type === self::DUNDEFINED ) {
// This case should be handled at a higher level, to avoid implicitly relying on what
// this method will do for the specific case.
throw new AFPException( 'Refusing to cast DUNDEFINED to something else' );
}
if ( $target === self::DNULL ) {
// We don't expose any method to cast to null. And, actually, should we?
return new AFPData( self::DNULL );
@ -340,8 +345,9 @@ class AFPData {
* @throws AFPException
*/
public static function boolOp( AFPData $a, AFPData $b, $op ) {
$a = $a->toBool();
$b = $b->toBool();
$a = $a->type === self::DUNDEFINED ? false : $a->toBool();
$b = $b->type === self::DUNDEFINED ? false : $b->toBool();
if ( $op === '|' ) {
return new AFPData( self::DBOOL, $a || $b );
} elseif ( $op === '&' ) {

View file

@ -63,9 +63,14 @@ class AbuseFilterCachingParser extends AbuseFilterParser {
}
);
return $tree
$res = $tree
? $this->evalNode( $tree )
: new AFPData( AFPData::DNULL, null );
if ( $res->getType() === AFPData::DUNDEFINED ) {
$res = new AFPData( AFPData::DBOOL, false );
}
return $res;
}
/**
@ -245,7 +250,7 @@ class AbuseFilterCachingParser extends AbuseFilterParser {
case AFPTreeNode::LOGIC:
list( $op, $leftOperand, $rightOperand ) = $node->children;
$leftOperand = $this->evalNode( $leftOperand );
$value = $leftOperand->toBool();
$value = $leftOperand->getType() === AFPData::DUNDEFINED ? false : $leftOperand->toBool();
// Short-circuit.
if ( ( !$value && $op === '&' ) || ( $value && $op === '|' ) ) {
if ( $rightOperand instanceof AFPTreeNode ) {

View file

@ -307,6 +307,10 @@ class AbuseFilterParser {
$result = new AFPData( AFPData::DEMPTY );
$this->doLevelEntry( $result );
if ( $result->getType() === AFPData::DUNDEFINED ) {
$result = new AFPData( AFPData::DBOOL, false );
}
return $result;
}
@ -560,9 +564,10 @@ class AbuseFilterParser {
$op = $this->mCur->value;
$this->move();
$r2 = new AFPData( AFPData::DEMPTY );
$curVal = $result->getType() === AFPData::DUNDEFINED ? false : $result->toBool();
// We can go on quickly as either one statement with | is true or one with & is false
if ( ( $op === '&' && !$result->toBool() ) || ( $op === '|' && $result->toBool() ) ) {
if ( ( $op === '&' && !$curVal ) || ( $op === '|' && $curVal ) ) {
$orig = $this->mShortCircuit;
$this->mShortCircuit = $this->mAllowShort;
$this->doLevelCompares( $r2 );
@ -570,7 +575,7 @@ class AbuseFilterParser {
$this->logEmptyOperand( 'bool operand', __METHOD__ );
}
$this->mShortCircuit = $orig;
$result = new AFPData( AFPData::DBOOL, $result->toBool() );
$result = new AFPData( AFPData::DBOOL, $curVal );
continue;
}