On 7/6/26 11:30 AM, Joyee Cheung wrote:
I came across this wrong-code bug when fixing the Node.js GCC build,
which was exposed by V8's use of an enum with a fixed bool underlying
type:
https://chromium-review.googlesource.com/c/v8/v8/+/7867245
This follows the same general approach as r13-3534-ge0997c14af5e8b,
using fold_convert_loc to give the converted value the enum type.
Bootstrapped and regtested on x86_64-pc-linux-gnu, OK for trunk?
-- >8 --
For an enumeration with a fixed underlying type, [expr.static.cast]/8
converts the operand to the underlying type before converting to the
enumeration type. If the underlying type is bool, a boolean conversion
should be used.
Previously, ocp_convert handled this case incorrectly by truncating
the operand. This patch fixes it using the existing bool conversion
path, and retypes the resulting bool value to the enum type. The
operand now goes through -Wint-in-bool-context diagnostics in the same
way as a cast to bool.
Since the conversion is well-defined when the enumeration type has
a fixed underlying type, the -Wconversion out-of-range warning is now
narrowed to enumeration types without a fixed underlying type,
consistent with explicit casts to the underlying type itself, which do
not warn.
PR c++/96496
gcc/cp/ChangeLog:
* cvt.cc (ocp_convert): Convert to an enumeration type with a
fixed underlying type of bool via cp_truthvalue_conversion, then
retype the result to the enum. Narrow the -Wconversion
out-of-range warning to enumeration types without a fixed
underlying type. Update the comment quoting
[expr.static.cast]/8.
gcc/testsuite/ChangeLog:
* g++.dg/cpp0x/enum-bool-conv.C: New test.
* g++.dg/warn/Wconversion-enum-fixed.C: New test.
* g++.dg/warn/Wint-in-bool-context-bool-enum.C: New test.
Signed-off-by: Joyee Cheung <[email protected]>
---
gcc/cp/cvt.cc | 39 ++++++-
gcc/testsuite/g++.dg/cpp0x/enum-bool-conv.C | 106 ++++++++++++++++++
.../g++.dg/warn/Wconversion-enum-fixed.C | 24 ++++
.../warn/Wint-in-bool-context-bool-enum.C | 13 +++
4 files changed, 176 insertions(+), 6 deletions(-)
create mode 100644 gcc/testsuite/g++.dg/cpp0x/enum-bool-conv.C
create mode 100644 gcc/testsuite/g++.dg/warn/Wconversion-enum-fixed.C
create mode 100644 gcc/testsuite/g++.dg/warn/Wint-in-bool-context-bool-enum.C
diff --git a/gcc/cp/cvt.cc b/gcc/cp/cvt.cc
index b18f66e582f..3f12a7d90ac 100644
--- a/gcc/cp/cvt.cc
+++ b/gcc/cp/cvt.cc
@@ -820,6 +820,15 @@ ocp_convert (tree type, tree expr, int convtype, int flags,
tree intype = TREE_TYPE (e);
tree converted;
+ /* If TYPE is an enumeration type with a fixed underlying type of
+ bool, [expr.static.cast]/8 requires the value to be converted
+ to that type first, which is a boolean conversion
+ ([conv.integral]/2, [conv.fpint]/1, [conv.bool]). */
+ const bool bool_enum_p
+ = (code == ENUMERAL_TYPE
+ && ENUM_FIXED_UNDERLYING_TYPE_P (type)
+ && TREE_CODE (ENUM_UNDERLYING_TYPE (type)) == BOOLEAN_TYPE);
+
if (TREE_CODE (type) == ENUMERAL_TYPE)
{
/* enum = enum, enum = int, enum = float, (enum)pointer are all
@@ -838,14 +847,25 @@ ocp_convert (tree type, tree expr, int convtype, int
flags,
/* [expr.static.cast]
8. A value of integral or enumeration type can be explicitly
- converted to an enumeration type. The value is unchanged if
- the original value is within the range of the enumeration
- values. Otherwise, the resulting enumeration value is
- unspecified. */
+ converted to a complete enumeration type. If the enumeration
+ type has a fixed underlying type, the value is first converted
+ to that type by integral promotion or integral conversion, if
+ necessary, and then to the enumeration type. If the
How about implementing this rule in general, rather than special-casing
bool?
+ enumeration type does not have a fixed underlying type, the
+ value is unchanged if the original value is within the range
+ of the enumeration values, and otherwise, the behavior is
+ undefined. A value of floating-point type can also be
+ explicitly converted to an enumeration type. The resulting
+ value is the same as converting the original value to the
+ underlying type of the enumeration, and subsequently to the
+ enumeration type. */
tree val = fold_for_warn (e);
if ((complain & tf_warning)
&& TREE_CODE (val) == INTEGER_CST
&& ENUM_UNDERLYING_TYPE (type)
+ /* Conversion to an enumeration type with a fixed underlying
+ type is well-defined, so do not warn. */
+ && !ENUM_FIXED_UNDERLYING_TYPE_P (type)
&& !int_fits_type_p (val, ENUM_UNDERLYING_TYPE (type)))
warning_at (loc, OPT_Wconversion,
"the result of the conversion is unspecified because "
@@ -862,7 +882,7 @@ ocp_convert (tree type, tree expr, int convtype, int flags,
error_at (loc, "%q#T used where a %qT was expected", intype, type);
return error_mark_node;
}
- if (code == BOOLEAN_TYPE)
+ if (code == BOOLEAN_TYPE || bool_enum_p)
{
if (VOID_TYPE_P (intype))
{
@@ -896,9 +916,16 @@ ocp_convert (tree type, tree expr, int convtype, int flags,
e = cp_truthvalue_conversion (e, complain);
}
+ if (e == error_mark_node)
+ return error_mark_node;
+
+ if (bool_enum_p)
+ /* The truthvalue conversion above already computed the value,
+ so retype it to the enum. */
+ e = fold_convert_loc (loc, type, e);
/* Sometimes boolean types don't match if a non-standard boolean
type has been invented by the target. */
- if (tree e2 = targetm.convert_to_type (type, e))
+ else if (tree e2 = targetm.convert_to_type (type, e))
return e2;
return e;
diff --git a/gcc/testsuite/g++.dg/cpp0x/enum-bool-conv.C
b/gcc/testsuite/g++.dg/cpp0x/enum-bool-conv.C
new file mode 100644
index 00000000000..1e762295609
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp0x/enum-bool-conv.C
@@ -0,0 +1,106 @@
+// PR c++/96496
+// { dg-do run { target c++11 } }
+// Conversion to an enum with a fixed underlying type of bool should convert
+// the value to bool first ([expr.static.cast]/8, CWG 2338).
+
+enum Flag : bool { kNo, kYes };
+enum class ScopedFlag : bool { kOff, kOn };
+
+struct S { constexpr operator Flag () const { return kYes; } };
+
+template <typename T> struct A
+{
+ enum E : T { kZero, kOne };
+};
+
+static_assert (static_cast<Flag> (2) == kYes, "");
+static_assert (static_cast<Flag> (-1) == kYes, "");
+static_assert ((Flag) 2 == kYes, "");
+static_assert (static_cast<Flag> (true) == kYes, "");
+static_assert (static_cast<Flag> (false) == kNo, "");
+static_assert (static_cast<Flag> (0) == kNo, "");
+static_assert (static_cast<Flag> (1) == kYes, "");
+static_assert (static_cast<Flag> (0.5) == kYes, "");
+static_assert (static_cast<Flag> (0.0) == kNo, "");
+static_assert (static_cast<ScopedFlag> (6) == ScopedFlag::kOn, "");
+static_assert (static_cast<Flag> (ScopedFlag::kOn) == kYes, "");
+static_assert (static_cast<Flag> (S{}) == kYes, "");
+static_assert (static_cast<A<bool>::E> (6) == A<bool>::kOne, "");
+static_assert (static_cast<A<char>::E> (6) == 6, "");
+
+// Helpers to keep the operands non-constant and exercise the
+// runtime conversion.
+
+Flag
+convert (int x)
+{
+ return static_cast<Flag> (x);
+}
+
+Flag
+convert (double d)
+{
+ return static_cast<Flag> (d);
+}
+
+Flag
+convert_cstyle (int x)
+{
+ return (Flag) x;
+}
+
+Flag
+convert_from_bool (bool b)
+{
+ return static_cast<Flag> (b);
+}
+
+ScopedFlag
+convert_scoped (int x)
+{
+ return static_cast<ScopedFlag> (x);
+}
+
+A<bool>::E
+convert_dep_bool (int x)
+{
+ return static_cast<A<bool>::E> (x);
+}
+
+A<char>::E
+convert_dep_char (int x)
+{
+ return static_cast<A<char>::E> (x);
+}
+
+Flag
+convert_from_scoped (ScopedFlag f)
+{
+ return static_cast<Flag> (f);
+}
+
+Flag
+convert_via_operator (S s)
+{
+ return static_cast<Flag> (s);
+}
+
+int
+main ()
+{
+ if (convert (2) != kYes) __builtin_abort ();
+ if (convert (-1) != kYes) __builtin_abort ();
+ if (convert (0) != kNo) __builtin_abort ();
+ if (convert (1) != kYes) __builtin_abort ();
+ if (convert (0.5) != kYes) __builtin_abort ();
+ if (convert (0.0) != kNo) __builtin_abort ();
+ if (convert_cstyle (2) != kYes) __builtin_abort ();
+ if (convert_cstyle (0) != kNo) __builtin_abort ();
+ if (convert_from_bool (true) != kYes) __builtin_abort ();
+ if (convert_from_bool (false) != kNo) __builtin_abort ();
+ if (convert_scoped (6) != ScopedFlag::kOn) __builtin_abort ();
+ if (convert_from_scoped (ScopedFlag::kOn) != kYes) __builtin_abort ();
+ if (convert_via_operator (S{}) != kYes) __builtin_abort ();
+ if (convert_dep_bool (6) != A<bool>::kOne) __builtin_abort ();
+ if (convert_dep_char (6) != 6) __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.dg/warn/Wconversion-enum-fixed.C
b/gcc/testsuite/g++.dg/warn/Wconversion-enum-fixed.C
new file mode 100644
index 00000000000..a72e232f16d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wconversion-enum-fixed.C
@@ -0,0 +1,24 @@
+// PR c++/96496
+// { dg-do compile { target c++11 } }
+// { dg-options "-Wconversion" }
+// Conversion to an enum with a fixed underlying type is well-defined
+// ([expr.static.cast]/8, CWG 2338), so -Wconversion should not warn.
+
+enum Flag : bool { kNo, kYes };
+enum C2 : unsigned char { C0 };
+enum SC : signed char { SC0 };
+enum class SI : short { SI0 };
+
+int f1 () { return (Flag) 6; } // { dg-bogus "outside the range" }
+int f2 () { return (C2) -1; } // { dg-bogus "outside the range" }
+int f3 () { return (C2) 257; } // { dg-bogus "outside the range" }
+int f4 () { return (SC) 128; } // { dg-bogus "outside the range" }
+int f5 () { return (int) (SI) 65537; } // { dg-bogus "outside the range" }
+int f6 (double d) { return (Flag) d; } // { dg-bogus "conversion" }
+int f7 () { return (Flag) 2.5; } // { dg-bogus "conversion" }
+
+// The conversion gives the same value as converting to the underlying type.
+static_assert (static_cast<C2> (257) == static_cast<unsigned char> (257), "");
+static_assert (static_cast<SC> (128) == static_cast<signed char> (128), "");
+static_assert (static_cast<SI> (65537)
+ == static_cast<SI> (static_cast<short> (65537)), "");
diff --git a/gcc/testsuite/g++.dg/warn/Wint-in-bool-context-bool-enum.C
b/gcc/testsuite/g++.dg/warn/Wint-in-bool-context-bool-enum.C
new file mode 100644
index 00000000000..c15faf12f60
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wint-in-bool-context-bool-enum.C
@@ -0,0 +1,13 @@
+// PR c++/96496
+// { dg-do compile { target c++11 } }
+// { dg-options "-Wint-in-bool-context" }
+// For static_cast<Flag>(expr), -Wint-in-bool-context applies similarly
+// to static_cast<bool>(expr).
+
+enum Flag : bool { kNo, kYes };
+
+Flag f1 (int x) { return static_cast<Flag> (x << 1); } // { dg-warning "in boolean
context" }
+Flag f2 (int x, int y) { return static_cast<Flag> (x * y); } // { dg-warning "in
boolean context" }
+Flag f3 (bool c) { return static_cast<Flag> (c ? 2 : 3); } // { dg-warning "in
boolean context" }
+Flag f4 (int x) { return static_cast<Flag> (x); } // { dg-bogus "in boolean
context" }
+Flag f5 (int x) { return static_cast<Flag> (x != 0); } // { dg-bogus "in boolean
context" }