Bug report
Summary
On GCC and clang, when compiling C , Py_MIN(), Py_MAX() and Py_ABS() are now GNU statement expressions that copy each argument into local variables named _x and _y. Code that used these public macros in an integer constant expression no longer compiles: file-scope array sizes, struct member array sizes, enum values and case labels all fail. Code that passes a bit-field also no longer compiles. When the caller passes a variable named _x or _y, the code compiles and the result is silently wrong: Py_MAX(a, _x) returns a, and Py_MIN(b, _y) and Py_ABS(_x) read an uninitialized local. Nested calls such as Py_MAX(Py_MIN(a, b), c) now emit -Wshadow warnings, and mixed-sign arguments such as Py_MIN(size_t_var, INT_MAX) emit -Wsign-compare warnings under GCC.
Reproduction Code
Requires GCC or clang compiling C. MSVC and C++ use the previous ternary definitions. The commands below use GCC 13.3.0 and clang 18.1.3 on x86-64 Linux, with -I pointing at a CPython source tree that includes the new Include/pymacro.h.
Wrong results:
/* wrong_result.c */
#include <Python.h>
#include <stdio.h>
int main(void)
{
int a = 1, _x = 5;
printf("Py_MAX(a, _x) = %d (expected 5)\n", Py_MAX(a, _x));
int b = 9, _y = 2;
printf("Py_MIN(b, _y) = %d (expected 2)\n", Py_MIN(b, _y));
int c = -7;
{
int _x = c;
printf("Py_ABS(_x) = %d (expected 7)\n", Py_ABS(_x));
}
return 0;
}
gcc -std=c11 -O0 -I Include -I . wrong_result.c -o t && ./t
gcc -std=c11 -O2 -I Include -I . wrong_result.c -o t && ./t
clang -std=c11 -O0 -I Include -I . wrong_result.c -o t && ./t
Compile failures and new warnings (each block is a separate file):
/* file_scope.c: compile with -c */
#include <Python.h>
static char buf[Py_MAX(sizeof(long), 16)];
struct S { char data[Py_MIN(8, 32)]; };
enum { E = Py_MAX(3, 7) };
/* bitfield.c: compile with -c */
#include <Python.h>
struct S { unsigned bf : 4; };
int f(struct S *s, int k) { return Py_MAX(s->bf, k); }
/* case_label.c: compile with -c */
#include <Python.h>
int f(int v)
{
switch (v) {
case Py_MAX(1, 2): return 1;
default: return 0;
}
}
/* shadow.c: compile with -c -Wall -Wextra -Wshadow */
#include <Python.h>
int f(int a, int b, int c) { return Py_MAX(Py_MIN(a, b), c); }
/* signcmp.c: compile with -c -Wall -Wextra */
#include <Python.h>
#include <limits.h>
int f(size_t n) { return (int)Py_MIN(n, INT_MAX); }
Actual Behavior
wrong_result.c output:
--- gcc -O0
Py_MAX(a, _x) = 1 (expected 5)
Py_MIN(b, _y) = -219883104 (expected 2)
Py_ABS(_x) = 32766 (expected 7)
--- gcc -O2
Py_MAX(a, _x) = 1 (expected 5)
Py_MIN(b, _y) = 0 (expected 2)
Py_ABS(_x) = 0 (expected 7)
--- clang -O0
Py_MAX(a, _x) = 1 (expected 5)
Py_MIN(b, _y) = 0 (expected 2)
Py_ABS(_x) = 0 (expected 7)
GCC compiles wrong_result.c without any warning at the default warning level. Under -Wall, clang warns variable '_y' is uninitialized when used within its own initialization [-Wuninitialized] and the same for _x. GCC gives no such warning.
Compile failures with GCC:
=== gcc -std=c11 -c file_scope.c
Include/pymacro.h:127:8: error: braced-group within expression allowed only inside a function
Include/pymacro.h:121:8: error: braced-group within expression allowed only inside a function
Include/pymacro.h:127:8: error: braced-group within expression allowed only inside a function
=== gcc -std=c11 -c bitfield.c
bitfield.c:4:43: error: ‘typeof’ applied to a bit-field
=== gcc -std=c11 -c case_label.c
case_label.c:6:5: error: case label does not reduce to an integer constant
Compile failures with clang:
=== clang -std=c11 -c file_scope.c
file_scope.c:3:17: error: statement expression not allowed at file scope
file_scope.c:4:22: error: statement expression not allowed at file scope
file_scope.c:5:12: error: statement expression not allowed at file scope
=== clang -std=c11 -c bitfield.c
bitfield.c:4:36: error: invalid application of 'typeof' to bit-field
=== clang -std=c11 -c case_label.c
case_label.c:6:10: error: expression is not an integer constant expression
New warnings:
=== gcc -std=c11 -Wall -Wextra -Wshadow -c shadow.c
Include/pymacro.h:121:26: warning: declaration of ‘_x’ shadows a previous local [-Wshadow]
=== gcc -std=c11 -Wall -Wextra -Wshadow -c signcmp.c
Include/pymacro.h:123:14: warning: comparison of integer expressions of different signedness: ‘size_t’ {aka ‘long unsigned int’} and ‘int’ [-Wsign-compare]
Include/pymacro.h:123:26: warning: operand of ‘?:’ changes signedness from ‘int’ to ‘size_t’ {aka ‘long unsigned int’} due to unsignedness of other operand [-Wsign-compare]
=== clang -std=c11 -Wall -Wextra -Wshadow -c shadow.c
shadow.c:3:44: warning: declaration shadows a local variable [-Wshadow]
clang emits no -Wsign-compare warning for signcmp.c.
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Linked PRs
Bug report
Summary
On GCC and clang, when compiling C ,
Py_MIN(),Py_MAX()andPy_ABS()are now GNU statement expressions that copy each argument into local variables named_xand_y. Code that used these public macros in an integer constant expression no longer compiles: file-scope array sizes, struct member array sizes,enumvalues andcaselabels all fail. Code that passes a bit-field also no longer compiles. When the caller passes a variable named_xor_y, the code compiles and the result is silently wrong:Py_MAX(a, _x)returnsa, andPy_MIN(b, _y)andPy_ABS(_x)read an uninitialized local. Nested calls such asPy_MAX(Py_MIN(a, b), c)now emit-Wshadowwarnings, and mixed-sign arguments such asPy_MIN(size_t_var, INT_MAX)emit-Wsign-comparewarnings under GCC.Reproduction Code
Requires GCC or clang compiling C. MSVC and C++ use the previous ternary definitions. The commands below use GCC 13.3.0 and clang 18.1.3 on x86-64 Linux, with
-Ipointing at a CPython source tree that includes the newInclude/pymacro.h.Wrong results:
Compile failures and new warnings (each block is a separate file):
Actual Behavior
wrong_result.coutput:GCC compiles
wrong_result.cwithout any warning at the default warning level. Under-Wall, clang warnsvariable '_y' is uninitialized when used within its own initialization [-Wuninitialized]and the same for_x. GCC gives no such warning.Compile failures with GCC:
Compile failures with clang:
New warnings:
clang emits no
-Wsign-comparewarning forsigncmp.c.CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Linked PRs
Py_MIN,Py_MAX, andPy_ABS#158943