Skip to content

Commit

Permalink
[3.12] gh-115011: Improve support of __index__() in setters of member…
Browse files Browse the repository at this point in the history
…s with unsigned integer type (GH-115029) (GH-115294)

Setters for members with an unsigned integer type now support
the same range of valid values for objects that has a __index__()
method as for int.

Previously, Py_T_UINT, Py_T_ULONG and Py_T_ULLONG did not support
objects that has a __index__() method larger than LONG_MAX.

Py_T_ULLONG did not support negative ints. Now it supports them and
emits a RuntimeWarning.
(cherry picked from commit d9d6909)
  • Loading branch information
serhiy-storchaka committed Feb 11, 2024
1 parent d65cd8b commit d8346d6
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 67 deletions.
44 changes: 14 additions & 30 deletions Lib/test/test_capi/test_structmembers.py
Expand Up @@ -81,36 +81,22 @@ def _test_int_range(self, name, minval, maxval, *, hardlimit=None,
self._test_warn(name, maxval+1, minval)
self._test_warn(name, hardmaxval)

if indexlimit is None:
indexlimit = hardlimit
if not indexlimit:
if indexlimit is False:
self.assertRaises(TypeError, setattr, ts, name, Index(minval))
self.assertRaises(TypeError, setattr, ts, name, Index(maxval))
else:
hardminindexval, hardmaxindexval = indexlimit
self._test_write(name, Index(minval), minval)
if minval < hardminindexval:
self._test_write(name, Index(hardminindexval), hardminindexval)
if maxval < hardmaxindexval:
self._test_write(name, Index(maxval), maxval)
else:
self._test_write(name, Index(hardmaxindexval), hardmaxindexval)
self._test_overflow(name, Index(hardminindexval-1))
if name in ('T_UINT', 'T_ULONG'):
self.assertRaises(TypeError, setattr, self.ts, name,
Index(hardmaxindexval+1))
self.assertRaises(TypeError, setattr, self.ts, name,
Index(2**1000))
else:
self._test_overflow(name, Index(hardmaxindexval+1))
self._test_overflow(name, Index(2**1000))
self._test_write(name, Index(maxval), maxval)
self._test_overflow(name, Index(hardminval-1))
self._test_overflow(name, Index(hardmaxval+1))
self._test_overflow(name, Index(2**1000))
self._test_overflow(name, Index(-2**1000))
if hardminindexval < minval and name != 'T_ULONGLONG':
self._test_warn(name, Index(hardminindexval))
self._test_warn(name, Index(minval-1))
if maxval < hardmaxindexval:
self._test_warn(name, Index(maxval+1))
self._test_warn(name, Index(hardmaxindexval))
if hardminval < minval:
self._test_warn(name, Index(hardminval))
self._test_warn(name, Index(minval-1), maxval)
if maxval < hardmaxval:
self._test_warn(name, Index(maxval+1), minval)
self._test_warn(name, Index(hardmaxval))

def test_bool(self):
ts = self.ts
Expand Down Expand Up @@ -138,22 +124,20 @@ def test_int(self):
self._test_int_range('T_INT', INT_MIN, INT_MAX,
hardlimit=(LONG_MIN, LONG_MAX))
self._test_int_range('T_UINT', 0, UINT_MAX,
hardlimit=(LONG_MIN, ULONG_MAX),
indexlimit=(LONG_MIN, LONG_MAX))
hardlimit=(LONG_MIN, ULONG_MAX))

def test_long(self):
self._test_int_range('T_LONG', LONG_MIN, LONG_MAX)
self._test_int_range('T_ULONG', 0, ULONG_MAX,
hardlimit=(LONG_MIN, ULONG_MAX),
indexlimit=(LONG_MIN, LONG_MAX))
hardlimit=(LONG_MIN, ULONG_MAX))

def test_py_ssize_t(self):
self._test_int_range('T_PYSSIZET', PY_SSIZE_T_MIN, PY_SSIZE_T_MAX, indexlimit=False)

def test_longlong(self):
self._test_int_range('T_LONGLONG', LLONG_MIN, LLONG_MAX)
self._test_int_range('T_ULONGLONG', 0, ULLONG_MAX,
indexlimit=(LONG_MIN, LONG_MAX))
hardlimit=(LONG_MIN, ULLONG_MAX))

def test_bad_assignments(self):
ts = self.ts
Expand Down
@@ -0,0 +1,3 @@
Setters for members with an unsigned integer type now support the same range
of valid values for objects that has a :meth:`~object.__index__` method as
for :class:`int`.
82 changes: 45 additions & 37 deletions Python/structmember.c
Expand Up @@ -3,6 +3,9 @@

#include "Python.h"
#include "structmember.h" // PyMemberDef
#include "pycore_abstract.h" // _PyNumber_Index()
#include "pycore_long.h" // _PyLong_IsNegative()


PyObject *
PyMember_GetOne(const char *obj_addr, PyMemberDef *l)
Expand Down Expand Up @@ -200,27 +203,22 @@ PyMember_SetOne(char *addr, PyMemberDef *l, PyObject *v)
case T_UINT: {
/* XXX: For compatibility, accept negative int values
as well. */
int overflow;
long long_val = PyLong_AsLongAndOverflow(v, &overflow);
if (long_val == -1 && PyErr_Occurred()) {
return -1;
}
if (overflow < 0) {
PyErr_SetString(PyExc_OverflowError,
"Python int too large to convert to C long");
v = _PyNumber_Index(v);
if (v == NULL) {
return -1;
}
else if (!overflow) {
*(unsigned int *)addr = (unsigned int)(unsigned long)long_val;
if (long_val < 0) {
WARN("Writing negative value into unsigned field");
}
else if ((unsigned long)long_val > UINT_MAX) {
WARN("Truncation of value to unsigned short");
if (_PyLong_IsNegative((PyLongObject *)v)) {
long long_val = PyLong_AsLong(v);
Py_DECREF(v);
if (long_val == -1 && PyErr_Occurred()) {
return -1;
}
*(unsigned int *)addr = (unsigned int)(unsigned long)long_val;
WARN("Writing negative value into unsigned field");
}
else {
unsigned long ulong_val = PyLong_AsUnsignedLong(v);
Py_DECREF(v);
if (ulong_val == (unsigned long)-1 && PyErr_Occurred()) {
return -1;
}
Expand All @@ -240,24 +238,22 @@ PyMember_SetOne(char *addr, PyMemberDef *l, PyObject *v)
case T_ULONG: {
/* XXX: For compatibility, accept negative int values
as well. */
int overflow;
long long_val = PyLong_AsLongAndOverflow(v, &overflow);
if (long_val == -1 && PyErr_Occurred()) {
return -1;
}
if (overflow < 0) {
PyErr_SetString(PyExc_OverflowError,
"Python int too large to convert to C long");
v = _PyNumber_Index(v);
if (v == NULL) {
return -1;
}
else if (!overflow) {
*(unsigned long *)addr = (unsigned long)long_val;
if (long_val < 0) {
WARN("Writing negative value into unsigned field");
if (_PyLong_IsNegative((PyLongObject *)v)) {
long long_val = PyLong_AsLong(v);
Py_DECREF(v);
if (long_val == -1 && PyErr_Occurred()) {
return -1;
}
*(unsigned long *)addr = (unsigned long)long_val;
WARN("Writing negative value into unsigned field");
}
else {
unsigned long ulong_val = PyLong_AsUnsignedLong(v);
Py_DECREF(v);
if (ulong_val == (unsigned long)-1 && PyErr_Occurred()) {
return -1;
}
Expand Down Expand Up @@ -313,18 +309,30 @@ PyMember_SetOne(char *addr, PyMemberDef *l, PyObject *v)
return -1;
break;
}
case T_ULONGLONG:{
unsigned long long value;
/* ??? PyLong_AsLongLong accepts an int, but PyLong_AsUnsignedLongLong
doesn't ??? */
if (PyLong_Check(v))
*(unsigned long long*)addr = value = PyLong_AsUnsignedLongLong(v);
else
*(unsigned long long*)addr = value = PyLong_AsLong(v);
if ((value == (unsigned long long)-1) && PyErr_Occurred())
case Py_T_ULONGLONG: {
v = _PyNumber_Index(v);
if (v == NULL) {
return -1;
break;
}
if (_PyLong_IsNegative((PyLongObject *)v)) {
long long_val = PyLong_AsLong(v);
Py_DECREF(v);
if (long_val == -1 && PyErr_Occurred()) {
return -1;
}
*(unsigned long long *)addr = (unsigned long long)(long long)long_val;
WARN("Writing negative value into unsigned field");
}
else {
unsigned long long ulonglong_val = PyLong_AsUnsignedLongLong(v);
Py_DECREF(v);
if (ulonglong_val == (unsigned long long)-1 && PyErr_Occurred()) {
return -1;
}
*(unsigned long long*)addr = ulonglong_val;
}
break;
}
default:
PyErr_Format(PyExc_SystemError,
"bad memberdescr type for %s", l->name);
Expand Down

0 comments on commit d8346d6

Please sign in to comment.