Fix compile-time/run-time discrepancies with unary operators
authorNikita Popov <nikita.ppv@gmail.com>
Tue, 15 Sep 2020 13:06:56 +0000 (15:06 +0200)
committerNikita Popov <nikita.ppv@gmail.com>
Tue, 15 Sep 2020 13:08:55 +0000 (15:08 +0200)
This addresses two issues:
 * ~ throws for a number of types, and we should not compile-time
   evaluate in that case. Add a check similar to what we do for
   binary ops.
 * Unary +/- may produce a different error message due to
   canonicalization of the constant operand to the RHS. To avoid
   this, put the constant operand on the RHS right away.

Fixes oss-fuzz #25649.

Zend/tests/numeric_strings/invalid_numeric_strings_must_generate_warning.phpt
Zend/tests/runtime_compile_time_binary_operands.phpt
Zend/zend_compile.c

index 3ffe78a119e9697a1e9c1278175953f5678617e4..2b76f538f46ed41f29d8e9cb2117cfcd0c10f9f1 100644 (file)
@@ -198,9 +198,9 @@ Unsupported operand types: int ^ string
 
 Warning: A non-numeric value encountered in %s on line %d
 int(149)
-Unsupported operand types: int * string
+Unsupported operand types: string * int
 ---
 
 Warning: A non-numeric value encountered in %s on line %d
 int(-151)
-Unsupported operand types: int * string
+Unsupported operand types: string * int
index 9fe76b2efbdce6d3e6cb90220fb72cfde5fef16c..14195141b111e95d4b366f57a275130fece7d76d 100644 (file)
@@ -5,7 +5,7 @@ memory_limit=256M
 --FILE--
 <?php
 
-$operands = [
+$binaryOperators = [
     "==",
     "!=",
     "===",
@@ -31,6 +31,12 @@ $operands = [
     "||",
     "&&",
 ];
+$unaryOperators = [
+    "~",
+    "-",
+    "+",
+    "!",
+];
 
 $input = [
     0,
@@ -100,8 +106,7 @@ function makeParam($param) {
 $c = 0;
 $f = 0;
 
-function prepareLine($op1, $op2, $cmp, $operator) {
-
+function prepareBinaryLine($op1, $op2, $cmp, $operator) {
     $op1_p = makeParam($op1);
     $op2_p = makeParam($op2);
 
@@ -118,6 +123,22 @@ function prepareLine($op1, $op2, $cmp, $operator) {
     }
     return $line;
 }
+function prepareUnaryLine($op, $cmp, $operator) {
+    $op_p = makeParam($op);
+
+    $error = "echo '" . addcslashes("$operator $op_p", "\\'") . '\', "\n"; $f++;';
+
+    $compare = "@($operator $op_p)";
+    $line = "\$c++; ";
+    try {
+        $result = makeParam($cmp());
+        $line .= "if (" . ($result === "(NAN)" ? "!is_nan($compare)" : "$compare !== $result") . ") { $error }";
+    } catch (Error $e) {
+        $msg = makeParam($e->getMessage());
+        $line .= "try { $compare; $error } catch (Error \$e) { if (\$e->getMessage() !== $msg) { $error } }";
+    }
+    return $line;
+}
 
 $filename = __DIR__ . DIRECTORY_SEPARATOR . 'compare_binary_operands_temp.php';
 $file = fopen($filename, "w");
@@ -126,14 +147,22 @@ fwrite($file, "<?php\n");
 
 foreach ($input as $left) {
     foreach ($input as $right) {
-        foreach ($operands as $operand) {
-            $line = prepareLine($left, $right, function() use ($left, $right, $operand) {
-                return eval("return @(\$left $operand \$right);");
-            }, $operand);
+        foreach ($binaryOperators as $operator) {
+            $line = prepareBinaryLine($left, $right, function() use ($left, $right, $operator) {
+                return eval("return @(\$left $operator \$right);");
+            }, $operator);
             fwrite($file, $line . "\n");
         }
     }
 }
+foreach ($input as $right) {
+    foreach ($unaryOperators as $operator) {
+        $line = prepareUnaryLine($right, function() use ($right, $operator) {
+            return eval("return @($operator \$right);");
+        }, $operator);
+        fwrite($file, $line . "\n");
+    }
+}
 
 fclose($file);
 
index 6d07abe3e0980ced99d9378fcd93a7cf9e3cfa15..719e7aaffeff04fe628dc6f3ee8b6d8c7a2a6d57 100644 (file)
@@ -7913,18 +7913,32 @@ static inline zend_bool zend_try_ct_eval_binary_op(zval *result, uint32_t opcode
 }
 /* }}} */
 
-static inline void zend_ct_eval_unary_op(zval *result, uint32_t opcode, zval *op) /* {{{ */
+zend_bool zend_unary_op_produces_error(uint32_t opcode, zval *op)
 {
+       if (opcode == ZEND_BW_NOT) {
+               return Z_TYPE_P(op) <= IS_TRUE || Z_TYPE_P(op) == IS_ARRAY;
+       }
+
+       return 0;
+}
+
+static inline zend_bool zend_try_ct_eval_unary_op(zval *result, uint32_t opcode, zval *op) /* {{{ */
+{
+       if (zend_unary_op_produces_error(opcode, op)) {
+               return 0;
+       }
+
        unary_op_type fn = get_unary_op(opcode);
        fn(result, op);
+       return 1;
 }
 /* }}} */
 
 static inline zend_bool zend_try_ct_eval_unary_pm(zval *result, zend_ast_kind kind, zval *op) /* {{{ */
 {
-       zval left;
-       ZVAL_LONG(&left, (kind == ZEND_AST_UNARY_PLUS) ? 1 : -1);
-       return zend_try_ct_eval_binary_op(result, ZEND_MUL, &left, op);
+       zval right;
+       ZVAL_LONG(&right, (kind == ZEND_AST_UNARY_PLUS) ? 1 : -1);
+       return zend_try_ct_eval_binary_op(result, ZEND_MUL, op, &right);
 }
 /* }}} */
 
@@ -8185,10 +8199,9 @@ void zend_compile_unary_op(znode *result, zend_ast *ast) /* {{{ */
        znode expr_node;
        zend_compile_expr(&expr_node, expr_ast);
 
-       if (expr_node.op_type == IS_CONST) {
+       if (expr_node.op_type == IS_CONST
+                       && zend_try_ct_eval_unary_op(&result->u.constant, opcode, &expr_node.u.constant)) {
                result->op_type = IS_CONST;
-               zend_ct_eval_unary_op(&result->u.constant, opcode,
-                       &expr_node.u.constant);
                zval_ptr_dtor(&expr_node.u.constant);
                return;
        }
@@ -8200,8 +8213,7 @@ void zend_compile_unary_op(znode *result, zend_ast *ast) /* {{{ */
 void zend_compile_unary_pm(znode *result, zend_ast *ast) /* {{{ */
 {
        zend_ast *expr_ast = ast->child[0];
-       znode expr_node;
-       znode lefthand_node;
+       znode expr_node, right_node;
 
        ZEND_ASSERT(ast->kind == ZEND_AST_UNARY_PLUS || ast->kind == ZEND_AST_UNARY_MINUS);
 
@@ -8214,9 +8226,9 @@ void zend_compile_unary_pm(znode *result, zend_ast *ast) /* {{{ */
                return;
        }
 
-       lefthand_node.op_type = IS_CONST;
-       ZVAL_LONG(&lefthand_node.u.constant, (ast->kind == ZEND_AST_UNARY_PLUS) ? 1 : -1);
-       zend_emit_op_tmp(result, ZEND_MUL, &lefthand_node, &expr_node);
+       right_node.op_type = IS_CONST;
+       ZVAL_LONG(&right_node.u.constant, (ast->kind == ZEND_AST_UNARY_PLUS) ? 1 : -1);
+       zend_emit_op_tmp(result, ZEND_MUL, &expr_node, &right_node);
 }
 /* }}} */
 
@@ -9752,7 +9764,9 @@ void zend_eval_const_expr(zend_ast **ast_ptr) /* {{{ */
                                return;
                        }
 
-                       zend_ct_eval_unary_op(&result, ast->attr, zend_ast_get_zval(ast->child[0]));
+                       if (!zend_try_ct_eval_unary_op(&result, ast->attr, zend_ast_get_zval(ast->child[0]))) {
+                               return;
+                       }
                        break;
                case ZEND_AST_UNARY_PLUS:
                case ZEND_AST_UNARY_MINUS: