Skip to content

Commit 4c4b5ce

Browse files
authored
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 #96673.
1 parent bee1070 commit 4c4b5ce

File tree

2 files changed

+50
-4
lines changed

2 files changed

+50
-4
lines changed

Lib/test/test_int.py

+39
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,16 @@
22
import time
33

44
import unittest
5+
from unittest import mock
56
from test import support
67
from test.test_grammar import (VALID_UNDERSCORE_LITERALS,
78
INVALID_UNDERSCORE_LITERALS)
89

10+
try:
11+
import _pylong
12+
except ImportError:
13+
_pylong = None
14+
915
L = [
1016
('0', 0),
1117
('1', 1),
@@ -841,6 +847,39 @@ def test_pylong_str_to_int(self):
841847
with self.assertRaises(ValueError) as err:
842848
int('_' + s)
843849

850+
@support.cpython_only # tests implementation details of CPython.
851+
@unittest.skipUnless(_pylong, "_pylong module required")
852+
@mock.patch.object(_pylong, "int_to_decimal_string")
853+
def test_pylong_misbehavior_error_path_to_str(
854+
self, mock_int_to_str):
855+
with support.adjust_int_max_str_digits(20_000):
856+
big_value = int('7'*19_999)
857+
mock_int_to_str.return_value = None # not a str
858+
with self.assertRaises(TypeError) as ctx:
859+
str(big_value)
860+
self.assertIn('_pylong.int_to_decimal_string did not',
861+
str(ctx.exception))
862+
mock_int_to_str.side_effect = RuntimeError("testABC")
863+
with self.assertRaises(RuntimeError):
864+
str(big_value)
865+
866+
@support.cpython_only # tests implementation details of CPython.
867+
@unittest.skipUnless(_pylong, "_pylong module required")
868+
@mock.patch.object(_pylong, "int_from_string")
869+
def test_pylong_misbehavior_error_path_from_str(
870+
self, mock_int_from_str):
871+
big_value = '7'*19_999
872+
with support.adjust_int_max_str_digits(20_000):
873+
mock_int_from_str.return_value = b'not an int'
874+
with self.assertRaises(TypeError) as ctx:
875+
int(big_value)
876+
self.assertIn('_pylong.int_from_string did not',
877+
str(ctx.exception))
878+
879+
mock_int_from_str.side_effect = RuntimeError("test123")
880+
with self.assertRaises(RuntimeError):
881+
int(big_value)
882+
844883

845884
if __name__ == "__main__":
846885
unittest.main()

Objects/longobject.c

+11-4
Original file line numberDiff line numberDiff line change
@@ -1753,7 +1753,11 @@ pylong_int_to_decimal_string(PyObject *aa,
17531753
if (s == NULL) {
17541754
goto error;
17551755
}
1756-
assert(PyUnicode_Check(s));
1756+
if (!PyUnicode_Check(s)) {
1757+
PyErr_SetString(PyExc_TypeError,
1758+
"_pylong.int_to_decimal_string did not return a str");
1759+
goto error;
1760+
}
17571761
if (writer) {
17581762
Py_ssize_t size = PyUnicode_GET_LENGTH(s);
17591763
if (_PyUnicodeWriter_Prepare(writer, size, '9') == -1) {
@@ -2362,6 +2366,7 @@ pylong_int_from_string(const char *start, const char *end, PyLongObject **res)
23622366
}
23632367
PyObject *s = PyUnicode_FromStringAndSize(start, end-start);
23642368
if (s == NULL) {
2369+
Py_DECREF(mod);
23652370
goto error;
23662371
}
23672372
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)
23712376
goto error;
23722377
}
23732378
if (!PyLong_Check(result)) {
2374-
PyErr_SetString(PyExc_TypeError, "an integer is required");
2379+
PyErr_SetString(PyExc_TypeError,
2380+
"_pylong.int_from_string did not return an int");
23752381
goto error;
23762382
}
23772383
*res = (PyLongObject *)result;
23782384
return 0;
23792385
error:
23802386
*res = NULL;
2381-
return 0;
2387+
return 0; // See the long_from_string_base() API comment.
23822388
}
23832389
#endif /* WITH_PYLONG_MODULE */
23842390

@@ -2617,7 +2623,8 @@ long_from_non_binary_base(const char *start, const char *end, Py_ssize_t digits,
26172623
* Return values:
26182624
*
26192625
* - Returns -1 on syntax error (exception needs to be set, *res is untouched)
2620-
* - Returns 0 and sets *res to NULL for MemoryError/OverflowError.
2626+
* - Returns 0 and sets *res to NULL for MemoryError, OverflowError, or
2627+
* _pylong.int_from_string() errors.
26212628
* - Returns 0 and sets *res to an unsigned, unnormalized PyLong (success!).
26222629
*
26232630
* Afterwards *str is set to point to the first non-digit (which may be *str!).

0 commit comments

Comments
 (0)