From 09fb71510fe0aca9f9cf0853461ad55c1712e6bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 10 Apr 2025 14:12:40 +0200 Subject: [PATCH 1/7] feat(semantics): use a macro for suppressing UB warnings --- Include/internal/pycore_emscripten_trampoline.h | 13 +++++++------ Include/methodobject.h | 12 ++++++++++-- Include/pyport.h | 1 + Objects/descrobject.c | 2 +- Objects/methodobject.c | 2 +- Objects/typeobject.c | 6 ++++-- Python/bytecodes.c | 10 ++++------ Python/executor_cases.c.h | 10 ++++------ Python/generated_cases.c.h | 10 ++++------ 9 files changed, 36 insertions(+), 30 deletions(-) diff --git a/Include/internal/pycore_emscripten_trampoline.h b/Include/internal/pycore_emscripten_trampoline.h index 7946eb5a74e974..16916f1a8eb16c 100644 --- a/Include/internal/pycore_emscripten_trampoline.h +++ b/Include/internal/pycore_emscripten_trampoline.h @@ -37,17 +37,18 @@ _PyEM_TrampolineCall(PyCFunctionWithKeywords func, PyObject* kw); #define _PyCFunction_TrampolineCall(meth, self, args) \ - _PyEM_TrampolineCall( \ - (*(PyCFunctionWithKeywords)(void(*)(void))(meth)), (self), (args), NULL) + _PyEM_TrampolineCall(*_PyCFunctionWithKeywords_CAST(meth), (self), (args), NULL) #define _PyCFunctionWithKeywords_TrampolineCall(meth, self, args, kw) \ _PyEM_TrampolineCall((meth), (self), (args), (kw)) -#define descr_set_trampoline_call(set, obj, value, closure) \ - ((int)_PyEM_TrampolineCall((PyCFunctionWithKeywords)(set), (obj), (value), (PyObject*)(closure))) +#define descr_set_trampoline_call(set, obj, value, closure) \ + ((int)_PyEM_TrampolineCall(_PyCFunctionWithKeywords_CAST(set), (obj), \ + (value), (PyObject*)(closure))) -#define descr_get_trampoline_call(get, obj, closure) \ - _PyEM_TrampolineCall((PyCFunctionWithKeywords)(get), (obj), (PyObject*)(closure), NULL) +#define descr_get_trampoline_call(get, obj, closure) \ + _PyEM_TrampolineCall(_PyCFunctionWithKeywords_CAST(get), (obj), \ + (PyObject*)(closure), NULL) #else // defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE) diff --git a/Include/methodobject.h b/Include/methodobject.h index cfff05f803309e..7b566299644fbc 100644 --- a/Include/methodobject.h +++ b/Include/methodobject.h @@ -49,8 +49,16 @@ typedef PyCFunctionFastWithKeywords _PyCFunctionFastWithKeywords; // used to prevent a compiler warning. If the function has a single parameter, // it triggers an undefined behavior when Python calls it with 2 parameters // (bpo-33012). -#define _PyCFunction_CAST(func) \ - _Py_CAST(PyCFunction, _Py_CAST(void(*)(void), (func))) +#define _PyCFunction_CAST(func) \ + _Py_FUNC_CAST(PyCFunction, func) +// Other casts are given for semantic conveniences, allowing +// users to see whether a cast to suppress a UB is necessary. +#define _PyCFunctionFast_CAST(func) \ + _Py_FUNC_CAST(PyCFunctionFast, func) +#define _PyCFunctionWithKeywords_CAST(func) \ + _Py_FUNC_CAST(PyCFunctionWithKeywords, func) +#define _PyCFunctionFastWithKeywords_CAST(func) \ + _Py_FUNC_CAST(PyCFunctionFastWithKeywords, func) PyAPI_FUNC(PyCFunction) PyCFunction_GetFunction(PyObject *); PyAPI_FUNC(PyObject *) PyCFunction_GetSelf(PyObject *); diff --git a/Include/pyport.h b/Include/pyport.h index 2a7192c2c55cdd..7b1d92a7969b73 100644 --- a/Include/pyport.h +++ b/Include/pyport.h @@ -35,6 +35,7 @@ #endif // Macro to use the more powerful/dangerous C-style cast even in C++. #define _Py_CAST(type, expr) ((type)(expr)) +#define _Py_FUNC_CAST(T, func) _Py_CAST(T, _Py_CAST(void(*)(void), (func))) // Static inline functions should use _Py_NULL rather than using directly NULL // to prevent C++ compiler warnings. On C23 and newer and on C++11 and newer, diff --git a/Objects/descrobject.c b/Objects/descrobject.c index 451e07f58e161e..268af0b217cd98 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -519,7 +519,7 @@ wrapperdescr_raw_call(PyWrapperDescrObject *descr, PyObject *self, wrapperfunc wrapper = descr->d_base->wrapper; if (descr->d_base->flags & PyWrapperFlag_KEYWORDS) { - wrapperfunc_kwds wk = (wrapperfunc_kwds)(void(*)(void))wrapper; + wrapperfunc_kwds wk = _Py_FUNC_CAST(wrapperfunc_kwds, wrapper); return (*wk)(self, args, descr->d_wrapped, kwds); } diff --git a/Objects/methodobject.c b/Objects/methodobject.c index 1f459dea44192c..0b3cc0c9d4230e 100644 --- a/Objects/methodobject.c +++ b/Objects/methodobject.c @@ -563,7 +563,7 @@ cfunction_call(PyObject *func, PyObject *args, PyObject *kwargs) PyObject *result; if (flags & METH_KEYWORDS) { result = _PyCFunctionWithKeywords_TrampolineCall( - (*(PyCFunctionWithKeywords)(void(*)(void))meth), + *_PyCFunctionWithKeywords_CAST(meth), self, args, kwargs); } else { diff --git a/Objects/typeobject.c b/Objects/typeobject.c index b92eaefc90d0af..162d44178cb028 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -10758,7 +10758,8 @@ static pytype_slotdef slotdefs[] = { "__repr__($self, /)\n--\n\nReturn repr(self)."), TPSLOT(__hash__, tp_hash, slot_tp_hash, wrap_hashfunc, "__hash__($self, /)\n--\n\nReturn hash(self)."), - FLSLOT(__call__, tp_call, slot_tp_call, (wrapperfunc)(void(*)(void))wrap_call, + FLSLOT(__call__, tp_call, slot_tp_call, + _Py_FUNC_CAST(wrapperfunc, wrap_call), "__call__($self, /, *args, **kwargs)\n--\n\nCall self as a function.", PyWrapperFlag_KEYWORDS), TPSLOT(__str__, tp_str, slot_tp_str, wrap_unaryfunc, @@ -10795,7 +10796,8 @@ static pytype_slotdef slotdefs[] = { TPSLOT(__delete__, tp_descr_set, slot_tp_descr_set, wrap_descr_delete, "__delete__($self, instance, /)\n--\n\nDelete an attribute of instance."), - FLSLOT(__init__, tp_init, slot_tp_init, (wrapperfunc)(void(*)(void))wrap_init, + FLSLOT(__init__, tp_init, slot_tp_init, + _Py_FUNC_CAST(wrapperfunc, wrap_call), "__init__($self, /, *args, **kwargs)\n--\n\n" "Initialize self. See help(type(self)) for accurate signature.", PyWrapperFlag_KEYWORDS), diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 53da324ee5ab89..96b72a5399dc3d 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -4117,7 +4117,7 @@ dummy_func( DECREF_INPUTS(); ERROR_IF(true, error); } - PyObject *res_o = ((PyCFunctionFast)(void(*)(void))cfunc)( + PyObject *res_o = _PyCFunctionFast_CAST(cfunc)( PyCFunction_GET_SELF(callable_o), args_o, total_args); @@ -4149,8 +4149,7 @@ dummy_func( STAT_INC(CALL, hit); /* res = func(self, arguments, nargs, kwnames) */ PyCFunctionFastWithKeywords cfunc = - (PyCFunctionFastWithKeywords)(void(*)(void)) - PyCFunction_GET_FUNCTION(callable_o); + _PyCFunctionFastWithKeywords_CAST(PyCFunction_GET_FUNCTION(callable_o)); STACKREFS_TO_PYOBJECTS(arguments, total_args, args_o); if (CONVERSION_FAILED(args_o)) { @@ -4318,7 +4317,7 @@ dummy_func( ERROR_IF(true, error); } PyCFunctionFastWithKeywords cfunc = - (PyCFunctionFastWithKeywords)(void(*)(void))meth->ml_meth; + _PyCFunctionFastWithKeywords_CAST(meth->ml_met); PyObject *res_o = cfunc(self, (args_o + 1), nargs, NULL); STACKREFS_TO_PYOBJECTS_CLEANUP(args_o); assert((res_o != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); @@ -4397,8 +4396,7 @@ dummy_func( DECREF_INPUTS(); ERROR_IF(true, error); } - PyCFunctionFast cfunc = - (PyCFunctionFast)(void(*)(void))meth->ml_meth; + PyCFunctionFast cfunc = _PyCFunctionFast_CAST(meth->ml_meth); PyObject *res_o = cfunc(self, (args_o + 1), nargs); STACKREFS_TO_PYOBJECTS_CLEANUP(args_o); assert((res_o != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 3457ff0d6a1c06..7aa0e4a22c3363 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -5472,7 +5472,7 @@ JUMP_TO_ERROR(); } _PyFrame_SetStackPointer(frame, stack_pointer); - PyObject *res_o = ((PyCFunctionFast)(void(*)(void))cfunc)( + PyObject *res_o = _PyCFunctionFast_CAST(cfunc)( PyCFunction_GET_SELF(callable_o), args_o, total_args); @@ -5532,8 +5532,7 @@ STAT_INC(CALL, hit); _PyFrame_SetStackPointer(frame, stack_pointer); PyCFunctionFastWithKeywords cfunc = - (PyCFunctionFastWithKeywords)(void(*)(void)) - PyCFunction_GET_FUNCTION(callable_o); + _PyCFunctionFastWithKeywords_CAST(PyCFunction_GET_FUNCTION(callable_o)); stack_pointer = _PyFrame_GetStackPointer(frame); STACKREFS_TO_PYOBJECTS(arguments, total_args, args_o); if (CONVERSION_FAILED(args_o)) { @@ -5883,7 +5882,7 @@ } _PyFrame_SetStackPointer(frame, stack_pointer); PyCFunctionFastWithKeywords cfunc = - (PyCFunctionFastWithKeywords)(void(*)(void))meth->ml_meth; + _PyCFunctionFastWithKeywords_CAST(meth->ml_met); PyObject *res_o = cfunc(self, (args_o + 1), nargs, NULL); stack_pointer = _PyFrame_GetStackPointer(frame); STACKREFS_TO_PYOBJECTS_CLEANUP(args_o); @@ -6038,8 +6037,7 @@ JUMP_TO_ERROR(); } _PyFrame_SetStackPointer(frame, stack_pointer); - PyCFunctionFast cfunc = - (PyCFunctionFast)(void(*)(void))meth->ml_meth; + PyCFunctionFast cfunc = _PyCFunctionFast_CAST(meth->ml_meth); PyObject *res_o = cfunc(self, (args_o + 1), nargs); stack_pointer = _PyFrame_GetStackPointer(frame); STACKREFS_TO_PYOBJECTS_CLEANUP(args_o); diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index fb4ab92c635d9e..a502a06f67fe62 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -2051,7 +2051,7 @@ JUMP_TO_LABEL(error); } _PyFrame_SetStackPointer(frame, stack_pointer); - PyObject *res_o = ((PyCFunctionFast)(void(*)(void))cfunc)( + PyObject *res_o = _PyCFunctionFast_CAST(cfunc)( PyCFunction_GET_SELF(callable_o), args_o, total_args); @@ -2144,8 +2144,7 @@ STAT_INC(CALL, hit); _PyFrame_SetStackPointer(frame, stack_pointer); PyCFunctionFastWithKeywords cfunc = - (PyCFunctionFastWithKeywords)(void(*)(void)) - PyCFunction_GET_FUNCTION(callable_o); + _PyCFunctionFastWithKeywords_CAST(PyCFunction_GET_FUNCTION(callable_o)); stack_pointer = _PyFrame_GetStackPointer(frame); STACKREFS_TO_PYOBJECTS(arguments, total_args, args_o); if (CONVERSION_FAILED(args_o)) { @@ -3380,8 +3379,7 @@ JUMP_TO_LABEL(error); } _PyFrame_SetStackPointer(frame, stack_pointer); - PyCFunctionFast cfunc = - (PyCFunctionFast)(void(*)(void))meth->ml_meth; + PyCFunctionFast cfunc = _PyCFunctionFast_CAST(meth->ml_meth); PyObject *res_o = cfunc(self, (args_o + 1), nargs); stack_pointer = _PyFrame_GetStackPointer(frame); STACKREFS_TO_PYOBJECTS_CLEANUP(args_o); @@ -3508,7 +3506,7 @@ } _PyFrame_SetStackPointer(frame, stack_pointer); PyCFunctionFastWithKeywords cfunc = - (PyCFunctionFastWithKeywords)(void(*)(void))meth->ml_meth; + _PyCFunctionFastWithKeywords_CAST(meth->ml_met); PyObject *res_o = cfunc(self, (args_o + 1), nargs, NULL); stack_pointer = _PyFrame_GetStackPointer(frame); STACKREFS_TO_PYOBJECTS_CLEANUP(args_o); From f6d6df36ba8bd21465fef9afd0b8d05b05555ec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 11 Apr 2025 17:19:15 +0200 Subject: [PATCH 2/7] fixup --- Python/bytecodes.c | 2 +- Python/executor_cases.c.h | 2 +- Python/generated_cases.c.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 209cdc26ee9e96..590a1a51851e2e 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -4370,7 +4370,7 @@ dummy_func( ERROR_IF(true, error); } PyCFunctionFastWithKeywords cfunc = - _PyCFunctionFastWithKeywords_CAST(meth->ml_met); + _PyCFunctionFastWithKeywords_CAST(meth->ml_meth); PyObject *res_o = cfunc(self, (args_o + 1), nargs, NULL); STACKREFS_TO_PYOBJECTS_CLEANUP(args_o); assert((res_o != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 45fdfab06a8ab4..659e66859b5c50 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -5917,7 +5917,7 @@ } _PyFrame_SetStackPointer(frame, stack_pointer); PyCFunctionFastWithKeywords cfunc = - _PyCFunctionFastWithKeywords_CAST(meth->ml_met); + _PyCFunctionFastWithKeywords_CAST(meth->ml_meth); PyObject *res_o = cfunc(self, (args_o + 1), nargs, NULL); stack_pointer = _PyFrame_GetStackPointer(frame); STACKREFS_TO_PYOBJECTS_CLEANUP(args_o); diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 5ccb9935e59121..8820d82ed96b29 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -3515,7 +3515,7 @@ } _PyFrame_SetStackPointer(frame, stack_pointer); PyCFunctionFastWithKeywords cfunc = - _PyCFunctionFastWithKeywords_CAST(meth->ml_met); + _PyCFunctionFastWithKeywords_CAST(meth->ml_meth); PyObject *res_o = cfunc(self, (args_o + 1), nargs, NULL); stack_pointer = _PyFrame_GetStackPointer(frame); STACKREFS_TO_PYOBJECTS_CLEANUP(args_o); From 21535077fc27d3ca4f280a21d2cd1b3ffcf3eeca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 11 Apr 2025 17:22:21 +0200 Subject: [PATCH 3/7] fixup --- Objects/typeobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 29b7d11cc3bbc5..00e01dbd18d3d6 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -10816,7 +10816,7 @@ static pytype_slotdef slotdefs[] = { wrap_descr_delete, "__delete__($self, instance, /)\n--\n\nDelete an attribute of instance."), FLSLOT(__init__, tp_init, slot_tp_init, - _Py_FUNC_CAST(wrapperfunc, wrap_call), + _Py_FUNC_CAST(wrapperfunc, wrap_init), "__init__($self, /, *args, **kwargs)\n--\n\n" "Initialize self. See help(type(self)) for accurate signature.", PyWrapperFlag_KEYWORDS), From efa7bdcb47376abdb5f7e5f4ea7d1fce16244363 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 18 Apr 2025 10:17:05 +0200 Subject: [PATCH 4/7] Update Include/methodobject.h Co-authored-by: Victor Stinner --- Include/methodobject.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/methodobject.h b/Include/methodobject.h index 7b566299644fbc..5371bcc499aeef 100644 --- a/Include/methodobject.h +++ b/Include/methodobject.h @@ -52,7 +52,7 @@ typedef PyCFunctionFastWithKeywords _PyCFunctionFastWithKeywords; #define _PyCFunction_CAST(func) \ _Py_FUNC_CAST(PyCFunction, func) // Other casts are given for semantic conveniences, allowing -// users to see whether a cast to suppress a UB is necessary. +// users to see whether a cast to suppress an undefined behavior is necessary. #define _PyCFunctionFast_CAST(func) \ _Py_FUNC_CAST(PyCFunctionFast, func) #define _PyCFunctionWithKeywords_CAST(func) \ From 4bcff7f00a01c074e042b4e7df086402f19edebf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 18 Apr 2025 10:22:45 +0200 Subject: [PATCH 5/7] address review --- Include/methodobject.h | 2 +- Include/pyport.h | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Include/methodobject.h b/Include/methodobject.h index 5371bcc499aeef..f172695af3d519 100644 --- a/Include/methodobject.h +++ b/Include/methodobject.h @@ -33,7 +33,7 @@ typedef PyObject *(*PyCMethod)(PyObject *, PyTypeObject *, PyObject *const *, typedef PyCFunctionFast _PyCFunctionFast; typedef PyCFunctionFastWithKeywords _PyCFunctionFastWithKeywords; -// Cast an function to the PyCFunction type to use it with PyMethodDef. +// Cast a function to the PyCFunction type to use it with PyMethodDef. // // This macro can be used to prevent compiler warnings if the first parameter // uses a different pointer type than PyObject* (ex: METH_VARARGS and METH_O diff --git a/Include/pyport.h b/Include/pyport.h index 7b1d92a7969b73..a3f50ec4b699c1 100644 --- a/Include/pyport.h +++ b/Include/pyport.h @@ -35,6 +35,14 @@ #endif // Macro to use the more powerful/dangerous C-style cast even in C++. #define _Py_CAST(type, expr) ((type)(expr)) +// Cast a function to another function type T. +// +// The macro first casts the function to the "void func(void)" type +// to prevent compiler warnings. +// +// Note that using this cast only prevents the compiler from emitting +// warnings, but does not prevent an undefined behavior at runtime if +// the original function signature is not respected. #define _Py_FUNC_CAST(T, func) _Py_CAST(T, _Py_CAST(void(*)(void), (func))) // Static inline functions should use _Py_NULL rather than using directly NULL From 398e139d02bcb79a8372800f1f278b4a51110ced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 18 Apr 2025 10:26:41 +0200 Subject: [PATCH 6/7] improve wording --- Include/methodobject.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Include/methodobject.h b/Include/methodobject.h index f172695af3d519..e6ec6421d1e59d 100644 --- a/Include/methodobject.h +++ b/Include/methodobject.h @@ -51,8 +51,9 @@ typedef PyCFunctionFastWithKeywords _PyCFunctionFastWithKeywords; // (bpo-33012). #define _PyCFunction_CAST(func) \ _Py_FUNC_CAST(PyCFunction, func) -// Other casts are given for semantic conveniences, allowing -// users to see whether a cast to suppress an undefined behavior is necessary. +// The macros below are given for semantic convenience, allowing users +// to see whether a cast to suppress an undefined behavior is necessary. +// Note: At runtime, the original function signature must be respected. #define _PyCFunctionFast_CAST(func) \ _Py_FUNC_CAST(PyCFunctionFast, func) #define _PyCFunctionWithKeywords_CAST(func) \ From 8f567fa599e4ec8ecf13078236130239974c062e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 18 Apr 2025 11:28:46 +0200 Subject: [PATCH 7/7] Update Include/pyport.h Co-authored-by: Victor Stinner --- Include/pyport.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Include/pyport.h b/Include/pyport.h index a3f50ec4b699c1..ebce31f1d14a01 100644 --- a/Include/pyport.h +++ b/Include/pyport.h @@ -35,6 +35,7 @@ #endif // Macro to use the more powerful/dangerous C-style cast even in C++. #define _Py_CAST(type, expr) ((type)(expr)) + // Cast a function to another function type T. // // The macro first casts the function to the "void func(void)" type