diff --git a/Include/pymacro.h b/Include/pymacro.h index 7ecce44a0d2a42..f31c9505a6a812 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 543a8096f16f8a..9054c9dfc1ed42 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 62ce81e2b510c7..c6c131976ff453 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 00000000000000..0e89171bba8657 --- /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 73b32c1d86c72e..9bddb9ce62d5b9 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 eb769294fd21db..577bb14df13405 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 41e1287c865070..b4f316ce0c06e4 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 0947d47c8a5558..9a41a422467223 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