diff --git a/Include/pymacro.h b/Include/pymacro.h index 7ecce44a0d2a428..f31c9505a6a812b 100644 --- a/Include/pymacro.h +++ b/Include/pymacro.h @@ -107,15 +107,40 @@ # endif #endif +#if ((defined(__GNUC__) || defined(__clang__)) \ + && defined(_Py_TYPEOF) && !defined(__cplusplus)) + // Implement Py_MIN(), Py_MAX() and Py_ABS() using _Py_TYPEOF() and + // statement expression to only evaluate each argument only once. + // It cannot be used in C++: ISO C++ forbids braced-groups within + // expressions. Statement expression is a GNU extension. Use __extension__ + // to avoid compiler warning in pedantic mode. -/* Minimum value between x and y */ -#define Py_MIN(x, y) (((x) > (y)) ? (y) : (x)) - -/* Maximum value between x and y */ -#define Py_MAX(x, y) (((x) > (y)) ? (x) : (y)) + /* Minimum value between x and y */ +# define Py_MIN(x, y) \ + __extension__ \ + ({ _Py_TYPEOF (x) _x = (x); \ + _Py_TYPEOF (y) _y = (y); \ + _x < _y ? _x : _y; }) + /* Maximum value between x and y */ +# define Py_MAX(x, y) \ + __extension__ \ + ({ _Py_TYPEOF (x) _x = (x); \ + _Py_TYPEOF (y) _y = (y); \ + _x > _y ? _x : _y; }) + /* Absolute value of the number x */ +# define Py_ABS(x) \ + __extension__ \ + ({ _Py_TYPEOF (x) _x = (x); \ + _x < 0 ? -_x : _x; }) +#else + /* Minimum value between x and y */ +# define Py_MIN(x, y) (((x) > (y)) ? (y) : (x)) + /* Maximum value between x and y */ +# define Py_MAX(x, y) (((x) > (y)) ? (x) : (y)) + /* Absolute value of the number x */ +# define Py_ABS(x) ((x) < 0 ? -(x) : (x)) +#endif -/* Absolute value of the number x */ -#define Py_ABS(x) ((x) < 0 ? -(x) : (x)) /* Safer implementation that avoids an undefined behavior for the minimal value of the signed integer type if its absolute value is larger than the maximal value of the signed integer type (in the two's complement diff --git a/Lib/test/test_cext/extension.c b/Lib/test/test_cext/extension.c index 543a8096f16f8aa..9054c9dfc1ed421 100644 --- a/Lib/test/test_cext/extension.c +++ b/Lib/test/test_cext/extension.c @@ -91,10 +91,15 @@ _testcext_exec(PyObject *module) if (!result) return -1; Py_DECREF(result); - // test Py_BUILD_ASSERT() and Py_BUILD_ASSERT_EXPR() + // Test Py_BUILD_ASSERT() and Py_BUILD_ASSERT_EXPR() Py_BUILD_ASSERT(sizeof(int) == sizeof(unsigned int)); assert(Py_BUILD_ASSERT_EXPR(sizeof(int) == sizeof(unsigned int)) == 0); + // Test Py_MIN(), Py_MAX(), Py_ABS() + assert(Py_MIN(5, 11) == 5); + assert(Py_MAX(5, 11) == 11); + assert(Py_ABS(-5) == 5); + // Test Py_CLEAR(): use typeof()/__typeof__() if available, or memcpy() obj = Py_None; Py_CLEAR(obj); diff --git a/Lib/test/test_cppext/extension.cpp b/Lib/test/test_cppext/extension.cpp index 62ce81e2b510c7a..c6c131976ff453b 100644 --- a/Lib/test/test_cppext/extension.cpp +++ b/Lib/test/test_cppext/extension.cpp @@ -294,6 +294,11 @@ _testcppext_exec(PyObject *module) Py_BUILD_ASSERT(sizeof(int) == sizeof(unsigned int)); assert(Py_BUILD_ASSERT_EXPR(sizeof(int) == sizeof(unsigned int)) == 0); + // Test Py_MIN(), Py_MAX(), Py_ABS() + assert(Py_MIN(5, 11) == 5); + assert(Py_MAX(5, 11) == 11); + assert(Py_ABS(-5) == 5); + // Test Py_CLEAR(): use typeof()/__typeof__() if available, or memcpy() PyObject *obj = Py_None; Py_CLEAR(obj); diff --git a/Misc/NEWS.d/next/C_API/2026-09-14-16-10-23.gh-issue-157495.LzMLtE.rst b/Misc/NEWS.d/next/C_API/2026-09-14-16-10-23.gh-issue-157495.LzMLtE.rst new file mode 100644 index 000000000000000..0e89171bba86579 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2026-09-14-16-10-23.gh-issue-157495.LzMLtE.rst @@ -0,0 +1,3 @@ +If ``typeof()`` is available, implement :c:macro:`Py_MIN`, :c:macro:`Py_MAX` +and :c:macro:`Py_ABS` using ``typeof()`` and statement expression to only +evaluate each argument once. Patch by Victor Stinner. diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 73b32c1d86c72ec..9bddb9ce62d5b99 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -5883,7 +5883,8 @@ _ssl_MemoryBIO_read_impl(PySSLMemoryBIO *self, int len) { int avail, nbytes; - avail = (int)Py_MIN(BIO_ctrl_pending(self->bio), INT_MAX); + size_t pending = BIO_ctrl_pending(self->bio); + avail = (int)Py_MIN(pending, (size_t)INT_MAX); if ((len < 0) || (len > avail)) len = avail; diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index eb769294fd21db8..577bb14df134059 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2045,12 +2045,22 @@ test_macros(PyObject *self, PyObject *Py_UNUSED(args)) static_assert(1 == 1, "bug"); Py_BUILD_ASSERT(1 == 1); - // Py_MIN(), Py_MAX(), Py_ABS() assert(Py_MIN(5, 11) == 5); assert(Py_MAX(5, 11) == 11); assert(Py_ABS(-5) == 5); +#if ((defined(__GNUC__) || defined(__clang__)) \ + && defined(_Py_TYPEOF) && !defined(__cplusplus)) + // When _Py_TYPEOF() is available, arguments are only evaluated once + int x = 5, y = 11; + assert(Py_MIN(++x, ++y) == 6); + x = 5; y = 11; + assert(Py_MAX(++x, ++y) == 12); + x = -5; + assert(Py_ABS(--x) == 6); +#endif + // Py_STRINGIFY() assert(strcmp(Py_STRINGIFY(123), "123") == 0); diff --git a/Modules/cjkcodecs/cjkcodecs.h b/Modules/cjkcodecs/cjkcodecs.h index 41e1287c8650701..b4f316ce0c06e46 100644 --- a/Modules/cjkcodecs/cjkcodecs.h +++ b/Modules/cjkcodecs/cjkcodecs.h @@ -163,7 +163,7 @@ get_module_state(PyObject *mod) do { \ Py_UCS4 _c1 = (c1); \ Py_UCS4 _c2 = (c2); \ - if (_PyUnicodeWriter_Prepare(writer, 2, Py_MAX(_c1, c2)) < 0) \ + if (_PyUnicodeWriter_Prepare(writer, 2, Py_MAX(_c1, _c2)) < 0) \ return MBERR_EXCEPTION; \ PyUnicode_WRITE(writer->kind, writer->data, writer->pos, _c1); \ PyUnicode_WRITE(writer->kind, writer->data, writer->pos + 1, _c2); \ diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c index 0947d47c8a55582..9a41a4224672233 100644 --- a/Objects/obmalloc.c +++ b/Objects/obmalloc.c @@ -3255,10 +3255,10 @@ _PyMem_DebugRawRealloc(void *ctx, void *p, size_t nbytes) } else { size_t i = original_nbytes - ERASED_SIZE; - memcpy(data, save, Py_MIN(nbytes, ERASED_SIZE)); + memcpy(data, save, Py_MIN(nbytes, (size_t)ERASED_SIZE)); if (nbytes > i) { memcpy(data + i, &save[ERASED_SIZE], - Py_MIN(nbytes - i, ERASED_SIZE)); + Py_MIN(nbytes - i, (size_t)ERASED_SIZE)); } } #endif