[pypy-commit] pypy default: Bug in the overflow detection code in the timeout argument to various

arigo pypy.commits at gmail.com
Mon Jul 18 04:28:40 EDT 2016


Author: Armin Rigo <arigo at tunes.org>
Branch: 
Changeset: r85748:47e0578e9553
Date: 2016-07-18 10:29 +0200
http://bitbucket.org/pypy/pypy/changeset/47e0578e9553/

Log:	Bug in the overflow detection code in the timeout argument to
	various thread/threading primitives. The cast from float to 64-bit
	integer needs to be done carefully, because floats don't have 64
	bits of precision.

diff --git a/pypy/module/thread/os_lock.py b/pypy/module/thread/os_lock.py
--- a/pypy/module/thread/os_lock.py
+++ b/pypy/module/thread/os_lock.py
@@ -9,12 +9,9 @@
 from pypy.interpreter.gateway import interp2app, unwrap_spec
 from pypy.interpreter.typedef import TypeDef
 from pypy.interpreter.error import oefmt
-from rpython.rlib.rarithmetic import r_longlong
+from rpython.rlib.rarithmetic import r_longlong, ovfcheck_float_to_longlong
 
 
-LONGLONG_MAX = r_longlong(2 ** (r_longlong.BITS-1) - 1)
-TIMEOUT_MAX = LONGLONG_MAX
-
 RPY_LOCK_FAILURE, RPY_LOCK_ACQUIRED, RPY_LOCK_INTR = range(3)
 
 def parse_acquire_args(space, blocking, timeout):
@@ -29,10 +26,12 @@
     elif timeout == -1.0:
         microseconds = -1
     else:
-        timeout *= 1e6
-        if timeout > float(TIMEOUT_MAX):
+        # 0.0 => 0.0, but otherwise tends to round up
+        timeout = timeout * 1e6 + 0.999
+        try:
+            microseconds = ovfcheck_float_to_longlong(timeout)
+        except OverflowError:
             raise oefmt(space.w_OverflowError, "timeout value is too large")
-        microseconds = r_longlong(timeout)
     return microseconds
 
 
@@ -45,7 +44,8 @@
             # Run signal handlers if we were interrupted
             space.getexecutioncontext().checksignals()
             if microseconds >= 0:
-                microseconds = r_longlong(endtime - (time.time() * 1e6))
+                microseconds = r_longlong((endtime - (time.time() * 1e6))
+                                          + 0.999)
                 # Check for negative values, since those mean block
                 # forever
                 if microseconds <= 0:
diff --git a/pypy/module/thread/test/test_lock.py b/pypy/module/thread/test/test_lock.py
--- a/pypy/module/thread/test/test_lock.py
+++ b/pypy/module/thread/test/test_lock.py
@@ -64,6 +64,25 @@
         else:
             assert self.runappdirect, "missing lock._py3k_acquire()"
 
+    def test_py3k_acquire_timeout_overflow(self):
+        import thread
+        lock = thread.allocate_lock()
+        if not hasattr(lock, '_py3k_acquire'):
+            skip("missing lock._py3k_acquire()")
+        maxint = 2**63 - 1
+        boundary = int(maxint * 1e-6)
+        for i in [-100000, -10000, -1000, -100, -10, -1, 0,
+                  1, 10, 100, 1000, 10000, 100000]:
+            timeout = (maxint + i) * 1e-6
+            try:
+                lock._py3k_acquire(True, timeout=timeout)
+            except OverflowError:
+                got_ovf = True
+            else:
+                got_ovf = False
+                lock.release()
+            assert (i, got_ovf) == (i, int(timeout * 1e6 + 0.999) > maxint)
+
     @py.test.mark.xfail(machine()=='s390x', reason='may fail under heavy load')
     def test_ping_pong(self):
         # The purpose of this test is that doing a large number of ping-pongs
diff --git a/rpython/rlib/rarithmetic.py b/rpython/rlib/rarithmetic.py
--- a/rpython/rlib/rarithmetic.py
+++ b/rpython/rlib/rarithmetic.py
@@ -14,6 +14,8 @@
          integer operation did overflow
 ovfcheck_float_to_int
          convert to an integer or raise OverflowError
+ovfcheck_float_to_longlong
+         convert to a longlong or raise OverflowError
 r_longlong
          like r_int but double word size
 r_ulonglong
@@ -182,6 +184,18 @@
 # int(float(i)) != i  because of rounding issues.
 # These are the minimum and maximum float value that can
 # successfully be casted to an int.
+
+# The following values are not quite +/-sys.maxint.
+# Note the "<= x <" here, as opposed to "< x <" above.
+# This is justified by test_typed in translator/c/test.
+def ovfcheck_float_to_longlong(x):
+    from rpython.rlib.rfloat import isnan
+    if isnan(x):
+        raise OverflowError
+    if -9223372036854776832.0 <= x < 9223372036854775296.0:
+        return r_longlong(x)
+    raise OverflowError
+
 if sys.maxint == 2147483647:
     def ovfcheck_float_to_int(x):
         from rpython.rlib.rfloat import isnan
@@ -191,16 +205,8 @@
             return int(x)
         raise OverflowError
 else:
-    # The following values are not quite +/-sys.maxint.
-    # Note the "<= x <" here, as opposed to "< x <" above.
-    # This is justified by test_typed in translator/c/test.
     def ovfcheck_float_to_int(x):
-        from rpython.rlib.rfloat import isnan
-        if isnan(x):
-            raise OverflowError
-        if -9223372036854776832.0 <= x < 9223372036854775296.0:
-            return int(x)
-        raise OverflowError
+        return int(ovfcheck_float_to_longlong(x))
 
 def compute_restype(self_type, other_type):
     if self_type is other_type:


More information about the pypy-commit mailing list