From 9b8d76317a93cb5d0ffbc90bde5a0d3c55d2f033 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Mon, 24 Nov 2025 14:10:04 +0100 Subject: [PATCH 1/2] On Python 3.13.0a6+, use the %T format in exception messages That avoids the responsibility for a possible raise in Py_TYPE(args[...])->tp_name when the GIL is disabled. Related to https://github.com/fedora-python/crypt_r/issues/16 --- src/_crypt_r.c | 14 ++++++++++++++ tests/test_crypt_r.py | 8 ++++++++ 2 files changed, 22 insertions(+) diff --git a/src/_crypt_r.c b/src/_crypt_r.c index 066ed53..675e68d 100644 --- a/src/_crypt_r.c +++ b/src/_crypt_r.c @@ -34,10 +34,17 @@ crypt_r_crypt(PyObject *module, PyObject *const *args, Py_ssize_t nargs) Py_ssize_t salt_length; if (!PyUnicode_Check(args[0])) { +#if PY_VERSION_HEX >= 0x030d00a6 + PyErr_Format( + PyExc_TypeError, "crypt argument 1 (word) must be string, not %T", + args[0] + ); +#else PyErr_Format( PyExc_TypeError, "crypt argument 1 (word) must be string, not %s", Py_TYPE(args[0])->tp_name ); +#endif return NULL; } word = PyUnicode_AsUTF8AndSize(args[0], &word_length); @@ -53,10 +60,17 @@ crypt_r_crypt(PyObject *module, PyObject *const *args, Py_ssize_t nargs) } if (!PyUnicode_Check(args[1])) { +#if PY_VERSION_HEX >= 0x030d00a6 + PyErr_Format( + PyExc_TypeError, "crypt argument 2 (salt) must be string, not %T", + args[1] + ); +#else PyErr_Format( PyExc_TypeError, "crypt argument 2 (salt) must be string, not %s", Py_TYPE(args[1])->tp_name ); +#endif return NULL; } salt = PyUnicode_AsUTF8AndSize(args[1], &salt_length); diff --git a/tests/test_crypt_r.py b/tests/test_crypt_r.py index ef8769e..10d93f3 100644 --- a/tests/test_crypt_r.py +++ b/tests/test_crypt_r.py @@ -85,6 +85,14 @@ def test_invalid_rounds(self): with self.assertRaisesRegex(ValueError, 'support'): self.crypt.mksalt(method, rounds=4096) + def test_invalid_types(self): + with self.assertRaisesRegex(TypeError, + r'^crypt argument 1 \(word\) must be string, not int$'): + self.crypt.crypt(42) + with self.assertRaisesRegex(TypeError, + r'^crypt argument 2 \(salt\) must be string, not int$'): + self.crypt.crypt('mypassword', 42) + if sys.version_info >= (3, 13): import crypt From ac55d7aa04c2402ec1bf0abf8f61e4ea8c743dc2 Mon Sep 17 00:00:00 2001 From: Lumir Balhar Date: Mon, 9 Nov 2026 13:37:24 +0100 Subject: [PATCH 2/2] Use a small helper to DRY --- src/_crypt_r.c | 43 +++++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/src/_crypt_r.c b/src/_crypt_r.c index 675e68d..aa1aff0 100644 --- a/src/_crypt_r.c +++ b/src/_crypt_r.c @@ -21,6 +21,25 @@ PyDoc_STRVAR(crypt_r_crypt__doc__, "will be used to perturb the encryption algorithm and produce distinct\n" "results for a given *word*."); +/* Set a TypeError complaining that argument `argno` (named `argname`) must be + * a string rather than the type of `obj`. Uses the %T format on Python + * 3.13.0a6+, falling back to tp_name on older versions. */ +static void +set_crypt_type_error(int argno, const char *argname, PyObject *obj) +{ +#if PY_VERSION_HEX >= 0x030d00a6 + PyErr_Format( + PyExc_TypeError, "crypt argument %d (%s) must be string, not %T", + argno, argname, obj + ); +#else + PyErr_Format( + PyExc_TypeError, "crypt argument %d (%s) must be string, not %s", + argno, argname, Py_TYPE(obj)->tp_name + ); +#endif +} + static PyObject * crypt_r_crypt(PyObject *module, PyObject *const *args, Py_ssize_t nargs) { @@ -34,17 +53,7 @@ crypt_r_crypt(PyObject *module, PyObject *const *args, Py_ssize_t nargs) Py_ssize_t salt_length; if (!PyUnicode_Check(args[0])) { -#if PY_VERSION_HEX >= 0x030d00a6 - PyErr_Format( - PyExc_TypeError, "crypt argument 1 (word) must be string, not %T", - args[0] - ); -#else - PyErr_Format( - PyExc_TypeError, "crypt argument 1 (word) must be string, not %s", - Py_TYPE(args[0])->tp_name - ); -#endif + set_crypt_type_error(1, "word", args[0]); return NULL; } word = PyUnicode_AsUTF8AndSize(args[0], &word_length); @@ -60,17 +69,7 @@ crypt_r_crypt(PyObject *module, PyObject *const *args, Py_ssize_t nargs) } if (!PyUnicode_Check(args[1])) { -#if PY_VERSION_HEX >= 0x030d00a6 - PyErr_Format( - PyExc_TypeError, "crypt argument 2 (salt) must be string, not %T", - args[1] - ); -#else - PyErr_Format( - PyExc_TypeError, "crypt argument 2 (salt) must be string, not %s", - Py_TYPE(args[1])->tp_name - ); -#endif + set_crypt_type_error(2, "salt", args[1]); return NULL; } salt = PyUnicode_AsUTF8AndSize(args[1], &salt_length);