public inbox for gdb-patches@sourceware.org
 help / color / mirror / Atom feed
* [PATCH] [gdbsupport] Fix -Wenum-constexpr-conversion in enum-flags.h
@ 2024-05-31 18:15 Carlos Galvez
  2024-06-14 10:42 ` [PING][PATCH] " Carlos Galvez
  2024-06-14 18:28 ` [PATCH] " Pedro Alves
  0 siblings, 2 replies; 5+ messages in thread
From: Carlos Galvez @ 2024-05-31 18:15 UTC (permalink / raw)
  To: gdb-patches; +Cc: Carlos Galvez

This fixes PR 31331:
https://sourceware.org/bugzilla/show_bug.cgi?id=31331

Currently, enum-flags.h is suppressing the warning -Wenum-constexpr-conversion
coming from recent versions of Clang. This warning is intended to be made a
compiler error (non-downgradeable) in future versions of Clang:

https://github.com/llvm/llvm-project/issues/59036

The rationale is that casting a value of an integral type into an enumeration
is Undefined Behavior if the value does not fit in the range of values of the
enum:
https://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1766

Undefined Behavior is not allowed in constant expressions, leading to an
ill-formed program.

In this case, in enum-flags.h, we are casting the value -1 to an enum of a
positive range only, which is UB as per the Standard and thus not allowed
in a constexpr context.

The purpose of doing this instead of using std::underlying_type is to detect
cases where a C-style enum (unscoped, without underlying type) would decay
(be promoted) to a signed integer.

The same can be achieved by explicitly forcing an integer promotion on the
enum, and evaluating its sign.

This approach is more generic and it actually uncovers existing issues, where
enums with unsigned small integer underlying types (e.g. unsigned char) are
also promoted to signed integers, for example:

  flags = flags & ~IS_PARENT_DEFERRED;

Where IS_PARENT_DEFERRED belongs to the following enum:

  enum enum cooked_index_flag_enum : unsigned char
  {
     ...
     IS_PARENT_DEFERRED = 16,
  };

Here, the enum is first promoted to signed int before applying operator~ on it,
which is what we want to avoid. These should be fixed in a follow-up patch, to
keep this one small and focused.

Since this is the only place where the warning was suppressed, remove also the
corresponding macro in include/diagnostics.h.

The change has been tested by running the entire gdb test suite (make check)
and comparing the results (testsuite/gdb.sum) against trunk. No noticeable
differences have been observed.
---
 gdbsupport/enum-flags.h | 126 ++++++++++++++++++++++++++++++++++++++--
 include/diagnostics.h   |   5 --
 2 files changed, 120 insertions(+), 11 deletions(-)

diff --git a/gdbsupport/enum-flags.h b/gdbsupport/enum-flags.h
index 50780043477..c6e0401b9b8 100644
--- a/gdbsupport/enum-flags.h
+++ b/gdbsupport/enum-flags.h
@@ -77,7 +77,56 @@
 
 /* Note that std::underlying_type<enum_type> is not what we want here,
    since that returns unsigned int even when the enum decays to signed
-   int.  */
+   int.
+
+   More specifically, we have the following use cases:
+
+   # 1
+   ---
+   We want enum_flags to behave in the same way as a raw enum
+   in the following context, which is pervasive in the codebase:
+
+   auto x = true ? Enum1() : Enum2();
+
+   Where Enum1 and Enum2 are C-style enums, i.e. unscoped and without
+   fixed underlying type. The output "x" will be of type "int" in this
+   case, even if std::underlying_type would yield "unsigned int" for
+   either enum.
+
+   The rationale for it is that Enum1 and Enum2 are different enum
+   types, so they must be converted to a common type. In order to achieve
+   this, "usual arithmethic conversions" are performed:
+
+   https://timsong-cpp.github.io/cppwp/n4140/expr.cond#6.2
+
+   Following that, "integral promotions" are performed on the operands:
+
+   https://timsong-cpp.github.io/cppwp/n4140/expr#10.5
+
+   Finally, unscoped enums without underlying type are converted to "int"
+   as first choice, if all the values fit in it:
+
+   https://timsong-cpp.github.io/cppwp/n4140/conv.prom#3
+
+   # 2
+   ---
+   We want to avoid applying unary operator~ on signed integer types,
+   since it can lead to implementation-defined or undefined behavior.
+
+   One example is:
+
+   flags = flags & ~ENUM_VALUE;
+
+   If ENUM_VALUE belongs to a C-style enum, integral promotions are
+   applied _before_ computing operator~:
+
+   https://timsong-cpp.github.io/cppwp/n4140/expr.unary.op#10
+
+   As mentioned previously, integral promotions will lead to a signed int
+   as first choice. operator~ would then be applied on a signed int,
+   which we want to avoid.
+
+   */
 template<int size, bool sign> class integer_for_size { typedef void type; };
 template<> struct integer_for_size<1, 0> { typedef uint8_t type; };
 template<> struct integer_for_size<2, 0> { typedef uint16_t type; };
@@ -88,15 +137,80 @@ template<> struct integer_for_size<2, 1> { typedef int16_t type; };
 template<> struct integer_for_size<4, 1> { typedef int32_t type; };
 template<> struct integer_for_size<8, 1> { typedef int64_t type; };
 
+/* Helper struct to determine whether an enum will be promoted to a
+   signed integer type when performing "usual arithmetic conversions".
+
+   "Usual arithmetic conversions" can happen in different contexts,
+   for example when using additive operators, so let's use that here.
+
+   https://timsong-cpp.github.io/cppwp/n4140/expr#add-1
+
+*/
+template<typename T>
+struct enum_will_be_promoted_to_signed_integer
+{
+  static_assert(std::is_enum<T>::value, "T must be an enum");
+  static constexpr bool value = std::is_signed<decltype(T()+T())>::value;
+};
+
+/* Helper struct to determine whether an enum has a small integer
+   as underlying type.
+*/
+template <typename T>
+struct enum_has_underlying_small_integer
+{
+  static_assert(std::is_enum<T>::value, "T must be an enum");
+  static constexpr bool value = sizeof(T) < sizeof(int);
+};
+
+/* Helper struct to determine which sign we want our custom
+   "enum_underlying_type" to have. We need to consider two aspects:
+
+   - The sign of the underlying type.
+   - The sign of the integer which the enum would be promoted into,
+     for example when used in arithmetic contexts.
+
+   Note: currently, we need to add an extra condition:
+   "!enum_has_underlying_small_integer".
+
+   This is because we have the following code on trunk:
+
+   flags = flags & ~IS_PARENT_DEFERRED;
+
+   Where IS_PARENT_DEFERRED belongs to a enum like:
+
+   enum enum cooked_index_flag_enum : unsigned char
+   {
+      ...
+      IS_PARENT_DEFERRED = 16,
+   };
+
+   Here, IS_PARENT_DEFERRED will _also_ be promoted to signed integer,
+   due to integral promotions on small integers:
+
+   https://timsong-cpp.github.io/cppwp/n4140/conv.prom#1
+
+   This is undesirable, because operator~ will be applied on a signed
+   integer, which is what we are trying to avoid.
+   Future work should remove this condition and fix the relevant code.
+*/
+template <typename T>
+struct enum_sign
+{
+  static_assert(std::is_enum<T>::value, "T must be an enum");
+  static constexpr bool value =
+    std::is_signed<typename std::underlying_type<T>::type>::value ||
+    (enum_will_be_promoted_to_signed_integer<T>::value &&
+    /* TODO: remove the condition below and fix affected code */
+    !enum_has_underlying_small_integer<T>::value);
+};
+
 template<typename T>
 struct enum_underlying_type
 {
-  DIAGNOSTIC_PUSH
-  DIAGNOSTIC_IGNORE_ENUM_CONSTEXPR_CONVERSION
   typedef typename
-    integer_for_size<sizeof (T), static_cast<bool>(T (-1) < T (0))>::type
-    type;
-  DIAGNOSTIC_POP
+  integer_for_size<sizeof (T), enum_sign<T>::value>::type
+  type;
 };
 
 namespace enum_flags_detail
diff --git a/include/diagnostics.h b/include/diagnostics.h
index 97e30ab807f..14575e20e27 100644
--- a/include/diagnostics.h
+++ b/include/diagnostics.h
@@ -76,11 +76,6 @@
 # define DIAGNOSTIC_ERROR_SWITCH \
   DIAGNOSTIC_ERROR ("-Wswitch")
 
-# if __has_warning ("-Wenum-constexpr-conversion")
-#  define DIAGNOSTIC_IGNORE_ENUM_CONSTEXPR_CONVERSION \
-   DIAGNOSTIC_IGNORE ("-Wenum-constexpr-conversion")
-# endif
-
 #elif defined (__GNUC__) /* GCC */
 
 # define DIAGNOSTIC_IGNORE_DEPRECATED_DECLARATIONS \
-- 
2.34.1


^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2024-06-25 19:43 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-05-31 18:15 [PATCH] [gdbsupport] Fix -Wenum-constexpr-conversion in enum-flags.h Carlos Galvez
2024-06-14 10:42 ` [PING][PATCH] " Carlos Galvez
2024-06-14 18:28 ` [PATCH] " Pedro Alves
2024-06-16 18:21   ` Carlos Galvez
2024-06-25 19:42     ` Carlos Galvez

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).