Skip to content

Commit cca9d1c

Browse files
[3.15] gh-153200: Fix math.isqrt() for int subclasses with overridden comparison operators (GH-153203) (GH-153223)
The final check-and-correct comparison in the arbitrary precision path could call a comparison operator overridden in an int subclass. Compare by value with int's tp_richcompare. (cherry picked from commit 3a1b547) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
1 parent 9d603d3 commit cca9d1c

3 files changed

Lines changed: 28 additions & 3 deletions

File tree

Lib/test/test_math_integer.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,19 @@ def __init__(self, value):
1515
def __index__(self):
1616
return self.value
1717

18+
# int subclass with broken arithmetic operators; implementations must
19+
# convert their arguments to exact ints instead of using these.
20+
class BadIntSubclass(int):
21+
def _binop(self, other='ignored', mod=None):
22+
return 42
23+
__add__ = __radd__ = __sub__ = __rsub__ = _binop
24+
__mul__ = __rmul__ = __mod__ = __rmod__ = _binop
25+
__divmod__ = __rdivmod__ = __pow__ = __rpow__ = _binop
26+
__floordiv__ = __rfloordiv__ = _binop
27+
__lshift__ = __rlshift__ = __rshift__ = __rrshift__ = _binop
28+
__and__ = __rand__ = __or__ = __ror__ = __xor__ = __rxor__ = _binop
29+
__lt__ = __le__ = __gt__ = __ge__ = _binop
30+
1831
# Here's a pure Python version of the math.integer.factorial algorithm, for
1932
# documentation and comparison purposes.
2033
#
@@ -226,6 +239,11 @@ def test_isqrt(self):
226239
self.assertIntEqual(isqrt(False), 0)
227240
self.assertIntEqual(isqrt(MyIndexable(1729)), 41)
228241

242+
# Overridden operators of an int subclass must not affect the
243+
# result.
244+
self.assertIntEqual(isqrt(BadIntSubclass(10**20)), 10**10)
245+
self.assertIntEqual(isqrt(BadIntSubclass(10**20 - 1)), 10**10 - 1)
246+
229247
with self.assertRaises(ValueError):
230248
isqrt(MyIndexable(-3))
231249

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :func:`math.isqrt` returning an incorrect result for arguments not
2+
less than 2**64 that are instances of an :class:`int` subclass with an
3+
overridden comparison operator.

Modules/mathintegermodule.c

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -454,16 +454,20 @@ math_integer_isqrt(PyObject *module, PyObject *n)
454454
/* The correct result is either a or a - 1. Figure out which, and
455455
decrement a if necessary. */
456456

457-
/* a_too_large = n < a * a */
457+
/* a_too_large = n < a * a. Compare by value: n can be an instance
458+
of an int subclass with an overridden __lt__ method. */
458459
b = PyNumber_Multiply(a, a);
459460
if (b == NULL) {
460461
goto error;
461462
}
462-
a_too_large = PyObject_RichCompareBool(n, b, Py_LT);
463+
PyObject *cmp = PyLong_Type.tp_richcompare(n, b, Py_LT);
463464
Py_DECREF(b);
464-
if (a_too_large == -1) {
465+
if (cmp == NULL) {
465466
goto error;
466467
}
468+
assert(PyBool_Check(cmp));
469+
a_too_large = (cmp == Py_True);
470+
Py_DECREF(cmp);
467471

468472
if (a_too_large) {
469473
Py_SETREF(a, PyNumber_Subtract(a, _PyLong_GetOne()));

0 commit comments

Comments
 (0)