]> granicus.if.org Git - php/commitdiff
Fixed bug #75938
authorNikita Popov <nikita.ppv@gmail.com>
Fri, 9 Feb 2018 14:10:23 +0000 (15:10 +0100)
committerNikita Popov <nikita.ppv@gmail.com>
Fri, 9 Feb 2018 14:10:23 +0000 (15:10 +0100)
New modulus range inference implementation has been verified using
https://gist.github.com/nikic/67947ff92cf0e1f7e931f2f0d4cf817f.

The computed bounds are not tight, but it seems to be very hard to
compute tight bounds on modulus operations.

NEWS
ext/opcache/Optimizer/zend_inference.c
ext/opcache/tests/bug75938.phpt [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index d5820fba5213221ea63f58b7e2935003d98338d5..40240283c8b66136d26f42119e62d7b2aa956262 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -33,6 +33,7 @@ PHP                                                                        NEWS
   . Fixed bug #75729 (opcache segfault when installing Bitrix). (Nikita)
   . Fixed bug #75893 (file_get_contents $http_response_header variable bugged
     with opcache). (Nikita)
+  . Fixed bug #75938 (Modulus value not stored in variable). (Nikita)
 
 - SPL:
   . Fixed bug #74519 (strange behavior of AppendIterator). (jhdxr)
index 2601b60f3c78dea3367a2732b0c172acee2326d1..9e58b9fd73fb5c700face6f4eb594e0fb879bea4 100644 (file)
@@ -501,6 +501,28 @@ static void zend_ssa_range_and(zend_long a, zend_long b, zend_long c, zend_long
        }
 }
 
+static inline zend_bool zend_abs_range(
+               zend_long min, zend_long max, zend_long *abs_min, zend_long *abs_max) {
+       if (min == ZEND_LONG_MIN) {
+               /* Cannot take absolute value of LONG_MIN  */
+               return 0;
+       }
+
+       if (min >= 0) {
+               *abs_min = min;
+               *abs_max = max;
+       } else if (max <= 0) {
+               *abs_min = -max;
+               *abs_max = -min;
+       } else {
+               /* Range crossing zero */
+               *abs_min = 0;
+               *abs_max = MAX(max, -min);
+       }
+
+       return 1;
+}
+
 /* Get the normal op corresponding to a compound assignment op */
 static inline zend_uchar get_compound_assign_op(zend_uchar opcode) {
        switch (opcode) {
@@ -648,20 +670,35 @@ static int zend_inference_calc_binary_op_range(
                                        tmp->min = ZEND_LONG_MIN;
                                        tmp->max = ZEND_LONG_MAX;
                                } else {
+                                       zend_long op2_abs_min, op2_abs_max;
+
                                        op1_min = OP1_MIN_RANGE();
                                        op2_min = OP2_MIN_RANGE();
                                        op1_max = OP1_MAX_RANGE();
                                        op2_max = OP2_MAX_RANGE();
-                                       if (op2_min == 0 || op2_max == 0) {
-                                               /* avoid division by zero */
+                                       if (!zend_abs_range(op2_min, op2_max, &op2_abs_min, &op2_abs_max)) {
                                                break;
                                        }
-                                       t1 = (op2_min == -1) ? 0 : (op1_min % op2_min);
-                                       t2 = (op2_max == -1) ? 0 : (op1_min % op2_max);
-                                       t3 = (op2_min == -1) ? 0 : (op1_max % op2_min);
-                                       t4 = (op2_max == -1) ? 0 : (op1_max % op2_max);
-                                       tmp->min = MIN(MIN(t1, t2), MIN(t3, t4));
-                                       tmp->max = MAX(MAX(t1, t2), MAX(t3, t4));
+
+                                       if (op2_abs_max == 0) {
+                                               /* Always modulus by zero, nothing we can do */
+                                               break;
+                                       }
+                                       if (op2_abs_min == 0) {
+                                               /* Ignore the modulus by zero case, which will throw */
+                                               op2_abs_min++;
+                                       }
+
+                                       if (op1_min >= 0) {
+                                               tmp->min = op1_max < op2_abs_min ? op1_min : 0;
+                                               tmp->max = MIN(op1_max, op2_abs_max - 1);
+                                       } else if (op1_max <= 0) {
+                                               tmp->min = MAX(op1_min, -op2_abs_max + 1);
+                                               tmp->max = op1_min > -op2_abs_min ? op1_max : 0;
+                                       } else {
+                                               tmp->min = MAX(op1_min, -op2_abs_max + 1);
+                                               tmp->max = MIN(op1_max, op2_abs_max - 1);
+                                       }
                                }
                                return 1;
                        }
diff --git a/ext/opcache/tests/bug75938.phpt b/ext/opcache/tests/bug75938.phpt
new file mode 100644 (file)
index 0000000..113745f
--- /dev/null
@@ -0,0 +1,16 @@
+--TEST--
+Bug #75938: Modulus value not stored in variable
+--FILE--
+<?php
+function borken($columns) {
+       $columns = (int) $columns;
+       if ($columns < 1) return 0;
+       $count  = count([1,2,3,4,5]);
+       var_dump($mod = ($count % $columns));
+       var_dump($mod);
+}
+borken(2);
+?>
+--EXPECT--
+int(1)
+int(1)