diff --git a/Include/pymacro.h b/Include/pymacro.h index 202b936d964f00..abb2f5f7489608 100644 --- a/Include/pymacro.h +++ b/Include/pymacro.h @@ -88,6 +88,7 @@ (uintptr_t)((a) - 1)) & ~(uintptr_t)((a) - 1))) /* Check if pointer "p" is aligned to "a"-bytes boundary. */ #define _Py_IS_ALIGNED(p, a) (!((uintptr_t)(p) & (uintptr_t)((a) - 1))) +#define _Py_IS_TYPE_UNSIGNED(type) (((type)-1) > (type)0) /* Use this for unused arguments in a function definition to silence compiler * warnings. Example: diff --git a/Include/pyport.h b/Include/pyport.h index 7137006870bf01..26c745f3507ae8 100644 --- a/Include/pyport.h +++ b/Include/pyport.h @@ -162,6 +162,27 @@ typedef int Py_ssize_clean_t; # define PY_FORMAT_SIZE_T "z" #endif + +/* _Py_HAVE_TYPEOF and _Py_TYPEOF can opportunistically support an equivalent + * of GCC's typeof extension where possible. It is not possible to get + * equivalent behavior on all platforms, so all uses of _Py_TYPEOF should be + * guarded by _Py_HAVE_TYPEOF. + */ +#if defined(__GNUC__) || defined(__clang__) || defined(__cplusplus) +# define _Py_HAVE_TYPEOF 1 +# if defined(__cplusplus) +# define _Py_TYPEOF(x) decltype(x) +# else +# define _Py_TYPEOF(x) __typeof__(x) +# endif +#else +# define _Py_TYPEOF(x) Py_FatalError( \ + "_Py_TYPEOF is not available in all supported compilation modes on " \ + "all supported compilers. Use the _Py_HAVE_TYPEOF macro to guard " \ + "any statements using _Py_TYPEOF." \ + ) +#endif + /* Py_LOCAL can be used instead of static to get the fastest possible calling * convention for functions that are local to a given module. * diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c index bee59b8d2ae0cc..6ec999c1b1d8d0 100644 --- a/Modules/_zoneinfo.c +++ b/Modules/_zoneinfo.c @@ -7,6 +7,17 @@ #include "datetime.h" +// This takes advantage of GCC compiler extensions to determine the type of the +// variable, so we'll make Py_ASSERT_VAR_UNSIGNED a noop on non-GCC platforms. +// This is not perfect, but it's better than nothing. +#ifdef __GNUC__ +#define IS_TYPE_UNSIGNED(type) (((type)-1) > (type)0) +#define Py_ASSERT_VAR_UNSIGNED(var) \ + Py_BUILD_ASSERT(IS_TYPE_UNSIGNED(__typeof__(var))) +#else +#define Py_ASSERT_VAR_UNSIGNED(var) +#endif + // Imports static PyObject *io_open = NULL; static PyObject *_tzpath_find_tzfile = NULL; @@ -1217,15 +1228,12 @@ calendarrule_new(uint8_t month, uint8_t week, uint8_t day, int8_t hour, return -1; } - // day is an unsigned integer, so day < 0 should always return false, but - // if day's type changes to a signed integer *without* changing this value, - // it may create a bug. Considering that the compiler should be able to - // optimize out the first comparison if day is an unsigned integer anyway, - // we will leave this comparison in place and disable the compiler warning. -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wtype-limits" - if (day < 0 || day > 6) { -#pragma GCC diagnostic pop + // The actual bounds of day are (day >= 0 && day <= 6), but since day is an + // unsigned variable, day >= 0 is always true. To ensure that a bug is not + // introduced in the event that someone changes day to a signed type, we + // will assert that it is an unsigned type. + Py_ASSERT_VAR_UNSIGNED(day); + if (day > 6) { PyErr_Format(PyExc_ValueError, "Day must be in [0, 6]"); return -1; }