Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion Include/internal/pycore_long.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ extern void _PyLong_FiniTypes(PyInterpreterState *interp);
# error "_PY_NSMALLPOSINTS must be greater than or equal to 257"
#endif

#define _PY_IS_SMALL_INT(val) \
(-_PY_NSMALLNEGINTS <= (val) && (val) < _PY_NSMALLPOSINTS)

// Return a reference to the immortal zero singleton.
// The function cannot return NULL.
static inline PyObject* _PyLong_GetZero(void)
Expand Down Expand Up @@ -224,6 +227,25 @@ _PyLong_IsPositive(const PyLongObject *op)
return (op->long_value.lv_tag & SIGN_MASK) == 0;
}

/* Return true if the argument is a small int */
static inline bool
_PyLong_IsSmallInt(const PyLongObject *op)
{
assert(PyLong_Check(op));
bool is_small_int = false;
if (_PyLong_IsCompact(op)) {
Py_ssize_t value = _PyLong_CompactValue(op);
if (_PY_IS_SMALL_INT(value)) {
PyLongObject *small_obj;
small_obj = &_PyLong_SMALL_INTS[_PY_NSMALLNEGINTS + value];
is_small_int = (op == small_obj);
}
}
assert(PyLong_CheckExact(op) || (!is_small_int));
assert(_Py_IsImmortal(op) || (!is_small_int));
return is_small_int;
}

static inline Py_ssize_t
_PyLong_DigitCount(const PyLongObject *op)
{
Expand Down Expand Up @@ -284,7 +306,9 @@ _PyLong_SetDigitCount(PyLongObject *op, Py_ssize_t size)
#define NON_SIZE_MASK ~((1 << NON_SIZE_BITS) - 1)

static inline void
_PyLong_FlipSign(PyLongObject *op) {
_PyLong_FlipSign(PyLongObject *op)
{
assert(!_PyLong_IsSmallInt(op));
unsigned int flipped_sign = 2 - (op->long_value.lv_tag & SIGN_MASK);
op->long_value.lv_tag &= NON_SIZE_MASK;
op->long_value.lv_tag |= flipped_sign;
Expand Down
10 changes: 10 additions & 0 deletions Lib/test/test_capi/test_long.py
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,16 @@ def test_long_fromnativebytes(self):
self.assertEqual(expect_u, fromnativebytes(v_be, n, 4, 1),
f"PyLong_FromNativeBytes(buffer, {n}, <big|unsigned>)")

def test_bug_143050(self):
with support.adjust_int_max_str_digits(0):
# Bug coming from using _pylong.int_from_string(), that
# currently requires > 6000 decimal digits.
int('-' + '0' * 7000, 10)
_testcapi.test_immortal_small_ints()
# Test also nonzero small int
int('-' + '0' * 7000 + '123', 10)
_testcapi.test_immortal_small_ints()


if __name__ == "__main__":
unittest.main()
15 changes: 14 additions & 1 deletion Modules/_testcapi/immortal.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#include "parts.h"

#define Py_BUILD_CORE
#include "internal/pycore_long.h" // _PyLong_IsSmallInt()

int verify_immortality(PyObject *object)
{
assert(_Py_IsImmortal(object));
Expand All @@ -26,7 +29,17 @@ static PyObject *
test_immortal_small_ints(PyObject *self, PyObject *Py_UNUSED(ignored))
{
for (int i = -5; i <= 256; i++) {
assert(verify_immortality(PyLong_FromLong(i)));
PyObject *obj = PyLong_FromLong(i);
assert(verify_immortality(obj));
int is_small_int = _PyLong_IsSmallInt((PyLongObject *)obj);
assert(is_small_int);
}
for (int i = 257; i <= 260; i++) {
PyObject *obj = PyLong_FromLong(i);
assert(obj);
int is_small_int = _PyLong_IsSmallInt((PyLongObject *)obj);
assert(!is_small_int);
Py_DECREF(obj);
}
Py_RETURN_NONE;
}
Expand Down
21 changes: 7 additions & 14 deletions Objects/longobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class int "PyObject *" "&PyLong_Type"

#define medium_value(x) ((stwodigits)_PyLong_CompactValue(x))

#define IS_SMALL_INT(ival) (-_PY_NSMALLNEGINTS <= (ival) && (ival) < _PY_NSMALLPOSINTS)
#define IS_SMALL_INT(ival) _PY_IS_SMALL_INT(ival)
#define IS_SMALL_UINT(ival) ((ival) < _PY_NSMALLPOSINTS)

#define _MAX_STR_DIGITS_ERROR_FMT_TO_INT "Exceeds the limit (%d digits) for integer string conversion: value has %zd digits; use sys.set_int_max_str_digits() to increase the limit"
Expand Down Expand Up @@ -3057,11 +3057,11 @@ PyLong_FromString(const char *str, char **pend, int base)
}

/* Set sign and normalize */
if (sign < 0) {
_PyLong_FlipSign(z);
}
long_normalize(z);
z = maybe_small_long(z);
if (sign < 0) {
_PyLong_Negate(&z);
}

if (pend != NULL) {
*pend = (char *)str;
Expand Down Expand Up @@ -3587,16 +3587,9 @@ long_dealloc(PyObject *self)
* we accidentally decref small Ints out of existence. Instead,
* since small Ints are immortal, re-set the reference count.
*/
PyLongObject *pylong = (PyLongObject*)self;
if (pylong && _PyLong_IsCompact(pylong)) {
stwodigits ival = medium_value(pylong);
if (IS_SMALL_INT(ival)) {
PyLongObject *small_pylong = (PyLongObject *)get_small_int((sdigit)ival);
if (pylong == small_pylong) {
_Py_SetImmortal(self);
return;
}
}
if (_PyLong_IsSmallInt((PyLongObject*)self)) {
_Py_SetImmortal(self);
return;
}
Py_TYPE(self)->tp_free(self);
}
Expand Down
Loading