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
4 changes: 4 additions & 0 deletions Include/internal/pycore_abstract.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ extern int _PyObject_RealIsSubclass(PyObject *derived, PyObject *cls);
// Export for '_bisect' shared extension.
PyAPI_FUNC(int) _Py_convert_optional_to_ssize_t(PyObject *, void *);

// Convert Python int to Py_ssize_t. Do nothing if the argument is None.
// Raises ValueError if argument is negative.
PyAPI_FUNC(int) _Py_convert_optional_to_non_negative_ssize_t(PyObject *, void *);

// Same as PyNumber_Index() but can return an instance of a subclass of int.
// Export for 'math' shared extension.
PyAPI_FUNC(PyObject*) _PyNumber_Index(PyObject *o);
Expand Down
70 changes: 64 additions & 6 deletions Lib/test/clinic.test.c
Original file line number Diff line number Diff line change
Expand Up @@ -1612,12 +1612,17 @@ test_Py_ssize_t_converter
a: Py_ssize_t = 12
b: Py_ssize_t(accept={int}) = 34
c: Py_ssize_t(accept={int, NoneType}) = 56
d: Py_ssize_t(accept={int}, allow_negative=False) = 78
e: Py_ssize_t(accept={int, NoneType}, allow_negative=False) = 90
f: Py_ssize_t(accept={int}, allow_negative=True) = -12
g: Py_ssize_t(accept={int, NoneType}, allow_negative=True) = -34
/

[clinic start generated code]*/

PyDoc_STRVAR(test_Py_ssize_t_converter__doc__,
"test_Py_ssize_t_converter($module, a=12, b=34, c=56, /)\n"
"test_Py_ssize_t_converter($module, a=12, b=34, c=56, d=78, e=90, f=-12,\n"
" g=-34, /)\n"
"--\n"
"\n");

Expand All @@ -1626,7 +1631,8 @@ PyDoc_STRVAR(test_Py_ssize_t_converter__doc__,

static PyObject *
test_Py_ssize_t_converter_impl(PyObject *module, Py_ssize_t a, Py_ssize_t b,
Py_ssize_t c);
Py_ssize_t c, Py_ssize_t d, Py_ssize_t e,
Py_ssize_t f, Py_ssize_t g);

static PyObject *
test_Py_ssize_t_converter(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
Expand All @@ -1635,8 +1641,12 @@ test_Py_ssize_t_converter(PyObject *module, PyObject *const *args, Py_ssize_t na
Py_ssize_t a = 12;
Py_ssize_t b = 34;
Py_ssize_t c = 56;
Py_ssize_t d = 78;
Py_ssize_t e = 90;
Py_ssize_t f = -12;
Py_ssize_t g = -34;

if (!_PyArg_CheckPositional("test_Py_ssize_t_converter", nargs, 0, 3)) {
if (!_PyArg_CheckPositional("test_Py_ssize_t_converter", nargs, 0, 7)) {
goto exit;
}
if (nargs < 1) {
Expand Down Expand Up @@ -1675,17 +1685,65 @@ test_Py_ssize_t_converter(PyObject *module, PyObject *const *args, Py_ssize_t na
if (!_Py_convert_optional_to_ssize_t(args[2], &c)) {
goto exit;
}
if (nargs < 4) {
goto skip_optional;
}
{
Py_ssize_t ival = -1;
PyObject *iobj = _PyNumber_Index(args[3]);
if (iobj != NULL) {
ival = PyLong_AsSsize_t(iobj);
Py_DECREF(iobj);
}
if (ival == -1 && PyErr_Occurred()) {
goto exit;
}
d = ival;
if (d < 0) {
PyErr_SetString(PyExc_ValueError,
"d cannot be negative");
goto exit;
}
}
if (nargs < 5) {
goto skip_optional;
}
if (!_Py_convert_optional_to_non_negative_ssize_t(args[4], &e)) {
goto exit;
}
if (nargs < 6) {
goto skip_optional;
}
{
Py_ssize_t ival = -1;
PyObject *iobj = _PyNumber_Index(args[5]);
if (iobj != NULL) {
ival = PyLong_AsSsize_t(iobj);
Py_DECREF(iobj);
}
if (ival == -1 && PyErr_Occurred()) {
goto exit;
}
f = ival;
}
if (nargs < 7) {
goto skip_optional;
}
if (!_Py_convert_optional_to_ssize_t(args[6], &g)) {
goto exit;
}
skip_optional:
return_value = test_Py_ssize_t_converter_impl(module, a, b, c);
return_value = test_Py_ssize_t_converter_impl(module, a, b, c, d, e, f, g);

exit:
return return_value;
}

static PyObject *
test_Py_ssize_t_converter_impl(PyObject *module, Py_ssize_t a, Py_ssize_t b,
Py_ssize_t c)
/*[clinic end generated code: output=48214bc3d01f4dd7 input=3855f184bb3f299d]*/
Py_ssize_t c, Py_ssize_t d, Py_ssize_t e,
Py_ssize_t f, Py_ssize_t g)
/*[clinic end generated code: output=4ae0a56a1447fba9 input=a25bac8ecf2890aa]*/


/*[clinic input]
Expand Down
21 changes: 19 additions & 2 deletions Lib/test/test_clinic.py
Original file line number Diff line number Diff line change
Expand Up @@ -2610,6 +2610,19 @@ def test_disallow_defining_class_at_module_level(self):
"""
self.expect_failure(block, err, lineno=2)

def test_allow_negative_accepted_by_py_ssize_t_converter_only(self):
errmsg = re.escape("converter_init() got an unexpected keyword argument 'allow_negative'")
unsupported_converters = [converter_name for converter_name in converters.keys()
if converter_name != "Py_ssize_t"]
for converter in unsupported_converters:
with self.subTest(converter=converter):
block = f"""
module m
m.func
a: {converter}(allow_negative=True)
"""
with self.assertRaisesRegex((AssertionError, TypeError), errmsg):
self.parse_function(block)

class ClinicExternalTest(TestCase):
maxDiff = None
Expand Down Expand Up @@ -3194,8 +3207,12 @@ def test_py_ssize_t_converter(self):
ac_tester.py_ssize_t_converter(PY_SSIZE_T_MAX + 1)
with self.assertRaises(TypeError):
ac_tester.py_ssize_t_converter([])
self.assertEqual(ac_tester.py_ssize_t_converter(), (12, 34, 56))
self.assertEqual(ac_tester.py_ssize_t_converter(1, 2, None), (1, 2, 56))
with self.assertRaises(ValueError):
ac_tester.py_ssize_t_converter(12, 34, 56, -1)
with self.assertRaises(ValueError):
ac_tester.py_ssize_t_converter(12, 34, 56, 78, -1)
self.assertEqual(ac_tester.py_ssize_t_converter(), (12, 34, 56, 78, 90, -12, -34))
self.assertEqual(ac_tester.py_ssize_t_converter(1, 2, None, 3, None, 4, None), (1, 2, 56, 3, 90, 4, -34))

def test_slice_index_converter(self):
from _testcapi import PY_SSIZE_T_MIN, PY_SSIZE_T_MAX
Expand Down
11 changes: 8 additions & 3 deletions Modules/_testclinic.c
Original file line number Diff line number Diff line change
Expand Up @@ -443,16 +443,21 @@ py_ssize_t_converter
a: Py_ssize_t = 12
b: Py_ssize_t(accept={int}) = 34
c: Py_ssize_t(accept={int, NoneType}) = 56
d: Py_ssize_t(accept={int}, allow_negative=False) = 78
e: Py_ssize_t(accept={int, NoneType}, allow_negative=False) = 90
f: Py_ssize_t(accept={int}, allow_negative=False) = -12
g: Py_ssize_t(accept={int, NoneType}, py_default="-34", allow_negative=False) = -34
/

[clinic start generated code]*/

static PyObject *
py_ssize_t_converter_impl(PyObject *module, Py_ssize_t a, Py_ssize_t b,
Py_ssize_t c)
/*[clinic end generated code: output=ce252143e0ed0372 input=76d0f342e9317a1f]*/
Py_ssize_t c, Py_ssize_t d, Py_ssize_t e,
Py_ssize_t f, Py_ssize_t g)
/*[clinic end generated code: output=ecf8e1a4a9abc95e input=7b7fa954780c1cb0]*/
{
RETURN_PACKED_ARGS(3, PyLong_FromSsize_t, Py_ssize_t, a, b, c);
RETURN_PACKED_ARGS(7, PyLong_FromSsize_t, Py_ssize_t, a, b, c, d, e, f, g);
}


Expand Down
68 changes: 63 additions & 5 deletions Modules/clinic/_testclinic.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions Python/modsupport.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,19 @@ _Py_convert_optional_to_ssize_t(PyObject *obj, void *result)
return 1;
}

int
_Py_convert_optional_to_non_negative_ssize_t(PyObject *obj, void *result)
{
if (!_Py_convert_optional_to_ssize_t(obj, result)) {
return 0;
}
if (obj != Py_None && *((Py_ssize_t *)result) < 0) {
PyErr_SetString(PyExc_ValueError, "argument cannot be negative");
return 0;
}
return 1;
}


/* Helper for mkvalue() to scan the length of a format */

Expand Down
37 changes: 29 additions & 8 deletions Tools/clinic/libclinic/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,21 +420,39 @@ class Py_ssize_t_converter(CConverter):
type = 'Py_ssize_t'
c_ignored_default = "0"

def converter_init(self, *, accept: TypeSet = {int}) -> None:
def converter_init(self, *, accept: TypeSet = {int},
allow_negative: bool = True) -> None:
self.allow_negative = allow_negative
if accept == {int}:
self.format_unit = 'n'
self.default_type = int
elif accept == {int, NoneType}:
self.converter = '_Py_convert_optional_to_ssize_t'
if self.allow_negative:
self.converter = '_Py_convert_optional_to_ssize_t'
else:
self.converter = '_Py_convert_optional_to_non_negative_ssize_t'
else:
fail(f"Py_ssize_t_converter: illegal 'accept' argument {accept!r}")

def use_converter(self) -> None:
if self.converter == '_Py_convert_optional_to_ssize_t':
self.add_include('pycore_abstract.h',
'_Py_convert_optional_to_ssize_t()')
if self.converter in {
'_Py_convert_optional_to_ssize_t',
'_Py_convert_optional_to_non_negative_ssize_t',
}:
self.add_include('pycore_abstract.h', f'{self.converter}()')

def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
if self.allow_negative:
non_negative_check = ''
else:
non_negative_check = self.format_code("""
if ({paramname} < 0) {{{{
PyErr_SetString(PyExc_ValueError,
"{paramname} cannot be negative");
goto exit;
}}}}""",
argname=argname,
)
if self.format_unit == 'n':
if limited_capi:
PyNumber_Index = 'PyNumber_Index'
Expand All @@ -452,11 +470,13 @@ def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> st
if (ival == -1 && PyErr_Occurred()) {{{{
goto exit;
}}}}
{paramname} = ival;
{paramname} = ival;{non_negative_check}
}}}}
""",
argname=argname,
PyNumber_Index=PyNumber_Index)
PyNumber_Index=PyNumber_Index,
non_negative_check=non_negative_check,
)
if not limited_capi:
return super().parse_arg(argname, displayname, limited_capi=limited_capi)
return self.format_code("""
Expand All @@ -465,7 +485,7 @@ def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> st
{paramname} = PyNumber_AsSsize_t({argname}, PyExc_OverflowError);
if ({paramname} == -1 && PyErr_Occurred()) {{{{
goto exit;
}}}}
}}}}{non_negative_check}
}}}}
else {{{{
{bad_argument}
Expand All @@ -475,6 +495,7 @@ def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> st
""",
argname=argname,
bad_argument=self.bad_argument(displayname, 'integer or None', limited_capi=limited_capi),
non_negative_check=non_negative_check,
)


Expand Down
Loading