Skip to content

Commit e36fe36

Browse files
miss-islingtonserhiy-storchakaclaude
authored
[3.13] gh-153200: Fix math.isqrt() for int subclasses with overridden comparison operators (GH-153203) (GH-153225) (GH-153226)
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) (cherry picked from commit a9a91d0) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent c0293f9 commit e36fe36

3 files changed

Lines changed: 33 additions & 3 deletions

File tree

Lib/test/test_math.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,19 @@ def __init__(self, value):
238238
def __index__(self):
239239
return self.value
240240

241+
# int subclass with broken arithmetic operators; implementations must
242+
# convert their arguments to exact ints instead of using these.
243+
class BadIntSubclass(int):
244+
def _binop(self, other='ignored', mod=None):
245+
return 42
246+
__add__ = __radd__ = __sub__ = __rsub__ = _binop
247+
__mul__ = __rmul__ = __mod__ = __rmod__ = _binop
248+
__divmod__ = __rdivmod__ = __pow__ = __rpow__ = _binop
249+
__floordiv__ = __rfloordiv__ = _binop
250+
__lshift__ = __rlshift__ = __rshift__ = __rrshift__ = _binop
251+
__and__ = __rand__ = __or__ = __ror__ = __xor__ = __rxor__ = _binop
252+
__lt__ = __le__ = __gt__ = __ge__ = _binop
253+
241254
class BadDescr:
242255
def __get__(self, obj, objtype=None):
243256
raise ValueError
@@ -1112,6 +1125,16 @@ def __index__(self):
11121125
self.assertIs(type(s), int)
11131126
self.assertEqual(s, 41)
11141127

1128+
# Overridden operators of an int subclass must not affect the
1129+
# result.
1130+
s = math.isqrt(BadIntSubclass(10**20))
1131+
self.assertIs(type(s), int)
1132+
self.assertEqual(s, 10**10)
1133+
1134+
s = math.isqrt(BadIntSubclass(10**20 - 1))
1135+
self.assertIs(type(s), int)
1136+
self.assertEqual(s, 10**10 - 1)
1137+
11151138
with self.assertRaises(ValueError):
11161139
math.isqrt(IntegerLike(-3))
11171140

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/mathmodule.c

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

1797-
/* a_too_large = n < a * a */
1797+
/* a_too_large = n < a * a. Compare by value: n can be an instance
1798+
of an int subclass with an overridden __lt__ method. */
17981799
b = PyNumber_Multiply(a, a);
17991800
if (b == NULL) {
18001801
goto error;
18011802
}
1802-
a_too_large = PyObject_RichCompareBool(n, b, Py_LT);
1803+
PyObject *cmp = PyLong_Type.tp_richcompare(n, b, Py_LT);
18031804
Py_DECREF(b);
1804-
if (a_too_large == -1) {
1805+
if (cmp == NULL) {
18051806
goto error;
18061807
}
1808+
assert(PyBool_Check(cmp));
1809+
a_too_large = (cmp == Py_True);
1810+
Py_DECREF(cmp);
18071811

18081812
if (a_too_large) {
18091813
Py_SETREF(a, PyNumber_Subtract(a, _PyLong_GetOne()));

0 commit comments

Comments
 (0)