[Python-checkins] gh-90716: bugfixes and more tests for _pylong. (#99073)

gpshead webhook-mailer at python.org
Thu Nov 3 19:18:45 EDT 2022


https://github.com/python/cpython/commit/4c4b5ce2e529a1279cd287e2d2d73ffcb6cf2ead
commit: 4c4b5ce2e529a1279cd287e2d2d73ffcb6cf2ead
branch: main
author: Gregory P. Smith <greg at krypto.org>
committer: gpshead <greg at krypto.org>
date: 2022-11-03T16:18:38-07:00
summary:

gh-90716: bugfixes and more tests for _pylong. (#99073)

* Properly decref on _pylong import error.
* Improve the error message on _pylong TypeError.
* Fix the assertion error in pydebug builds to be a TypeError.
* Tie the return value comments together.

These are minor followups to issues not caught among the reviewers on
https://github.com/python/cpython/pull/96673.

files:
M Lib/test/test_int.py
M Objects/longobject.c

diff --git a/Lib/test/test_int.py b/Lib/test/test_int.py
index f484c59f675f..334fea0774be 100644
--- a/Lib/test/test_int.py
+++ b/Lib/test/test_int.py
@@ -2,10 +2,16 @@
 import time
 
 import unittest
+from unittest import mock
 from test import support
 from test.test_grammar import (VALID_UNDERSCORE_LITERALS,
                                INVALID_UNDERSCORE_LITERALS)
 
+try:
+    import _pylong
+except ImportError:
+    _pylong = None
+
 L = [
         ('0', 0),
         ('1', 1),
@@ -841,6 +847,39 @@ def test_pylong_str_to_int(self):
         with self.assertRaises(ValueError) as err:
             int('_' + s)
 
+    @support.cpython_only  # tests implementation details of CPython.
+    @unittest.skipUnless(_pylong, "_pylong module required")
+    @mock.patch.object(_pylong, "int_to_decimal_string")
+    def test_pylong_misbehavior_error_path_to_str(
+            self, mock_int_to_str):
+        with support.adjust_int_max_str_digits(20_000):
+            big_value = int('7'*19_999)
+            mock_int_to_str.return_value = None  # not a str
+            with self.assertRaises(TypeError) as ctx:
+                str(big_value)
+            self.assertIn('_pylong.int_to_decimal_string did not',
+                          str(ctx.exception))
+            mock_int_to_str.side_effect = RuntimeError("testABC")
+            with self.assertRaises(RuntimeError):
+                str(big_value)
+
+    @support.cpython_only  # tests implementation details of CPython.
+    @unittest.skipUnless(_pylong, "_pylong module required")
+    @mock.patch.object(_pylong, "int_from_string")
+    def test_pylong_misbehavior_error_path_from_str(
+            self, mock_int_from_str):
+        big_value = '7'*19_999
+        with support.adjust_int_max_str_digits(20_000):
+            mock_int_from_str.return_value = b'not an int'
+            with self.assertRaises(TypeError) as ctx:
+                int(big_value)
+            self.assertIn('_pylong.int_from_string did not',
+                          str(ctx.exception))
+
+            mock_int_from_str.side_effect = RuntimeError("test123")
+            with self.assertRaises(RuntimeError):
+                int(big_value)
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/Objects/longobject.c b/Objects/longobject.c
index cf859ccb9707..a87293899001 100644
--- a/Objects/longobject.c
+++ b/Objects/longobject.c
@@ -1753,7 +1753,11 @@ pylong_int_to_decimal_string(PyObject *aa,
     if (s == NULL) {
         goto error;
     }
-    assert(PyUnicode_Check(s));
+    if (!PyUnicode_Check(s)) {
+        PyErr_SetString(PyExc_TypeError,
+                        "_pylong.int_to_decimal_string did not return a str");
+        goto error;
+    }
     if (writer) {
         Py_ssize_t size = PyUnicode_GET_LENGTH(s);
         if (_PyUnicodeWriter_Prepare(writer, size, '9') == -1) {
@@ -2362,6 +2366,7 @@ pylong_int_from_string(const char *start, const char *end, PyLongObject **res)
     }
     PyObject *s = PyUnicode_FromStringAndSize(start, end-start);
     if (s == NULL) {
+        Py_DECREF(mod);
         goto error;
     }
     PyObject *result = PyObject_CallMethod(mod, "int_from_string", "O", s);
@@ -2371,14 +2376,15 @@ pylong_int_from_string(const char *start, const char *end, PyLongObject **res)
         goto error;
     }
     if (!PyLong_Check(result)) {
-        PyErr_SetString(PyExc_TypeError, "an integer is required");
+        PyErr_SetString(PyExc_TypeError,
+                        "_pylong.int_from_string did not return an int");
         goto error;
     }
     *res = (PyLongObject *)result;
     return 0;
 error:
     *res = NULL;
-    return 0;
+    return 0;  // See the long_from_string_base() API comment.
 }
 #endif /* WITH_PYLONG_MODULE */
 
@@ -2617,7 +2623,8 @@ long_from_non_binary_base(const char *start, const char *end, Py_ssize_t digits,
  * Return values:
  *
  *   - Returns -1 on syntax error (exception needs to be set, *res is untouched)
- *   - Returns 0 and sets *res to NULL for MemoryError/OverflowError.
+ *   - Returns 0 and sets *res to NULL for MemoryError, OverflowError, or
+ *     _pylong.int_from_string() errors.
  *   - Returns 0 and sets *res to an unsigned, unnormalized PyLong (success!).
  *
  * Afterwards *str is set to point to the first non-digit (which may be *str!).



More information about the Python-checkins mailing list