FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

`Py_MIN()`, `Py_MAX()` and `Py_ABS()` cause C compatibility regressions · Issue #158942 · python/cpython · GitHub

Repository navigation

Py_MIN(), Py_MAX() and Py_ABS() cause C compatibility regressions #158942

Description

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.16new features, bugs and security fixesbuildThe build process and cross-buildinterpreter-core(Objects, Python, Grammar, and Parser dirs)topic-C-APItype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions


      Back | FazBrowse Home | New Git URL