diff --git a/includes/parser/AFPData.php b/includes/parser/AFPData.php index 79288a3b1..8f46f4465 100644 --- a/includes/parser/AFPData.php +++ b/includes/parser/AFPData.php @@ -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 === '&' ) { diff --git a/includes/parser/AbuseFilterCachingParser.php b/includes/parser/AbuseFilterCachingParser.php index f3be7a913..159248156 100644 --- a/includes/parser/AbuseFilterCachingParser.php +++ b/includes/parser/AbuseFilterCachingParser.php @@ -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 ) { diff --git a/includes/parser/AbuseFilterParser.php b/includes/parser/AbuseFilterParser.php index 31469ecbd..b2c2facf2 100644 --- a/includes/parser/AbuseFilterParser.php +++ b/includes/parser/AbuseFilterParser.php @@ -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; }