Skip to content

[clang] Check constexpr int->enum conversions consistently. #143034

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 6, 2025

Conversation

efriedma-quic
Copy link
Collaborator

In 8de5137 and related patches, we added some code to avoid triggering -Wenum-constexpr-conversion in some cases. This isn't necessary anymore because -Wenum-constexpr-conversion doesn't exist anymore. And the checks are subtly wrong: they exclude cases where we actually do need to check the conversion. This patch gets rid of the unnecessary checks.

@efriedma-quic efriedma-quic requested a review from shafik June 5, 2025 21:00
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Jun 5, 2025
@llvmbot
Copy link
Member

llvmbot commented Jun 5, 2025

@llvm/pr-subscribers-clang

Author: Eli Friedman (efriedma-quic)

Changes

In 8de5137 and related patches, we added some code to avoid triggering -Wenum-constexpr-conversion in some cases. This isn't necessary anymore because -Wenum-constexpr-conversion doesn't exist anymore. And the checks are subtly wrong: they exclude cases where we actually do need to check the conversion. This patch gets rid of the unnecessary checks.


Full diff: https://github.com/llvm/llvm-project/pull/143034.diff

2 Files Affected:

  • (modified) clang/lib/AST/ExprConstant.cpp (+3-17)
  • (modified) clang/test/SemaCXX/constant-expression-cxx11.cpp (+7)
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index ab964e592de80..3417e47e33db6 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -15223,21 +15223,7 @@ bool IntExprEvaluator::VisitCastExpr(const CastExpr *E) {
       return Info.Ctx.getTypeSize(DestType) == Info.Ctx.getTypeSize(SrcType);
     }
 
-    if (Info.Ctx.getLangOpts().CPlusPlus && Info.InConstantContext &&
-        Info.EvalMode == EvalInfo::EM_ConstantExpression &&
-        DestType->isEnumeralType()) {
-
-      bool ConstexprVar = true;
-
-      // We know if we are here that we are in a context that we might require
-      // a constant expression or a context that requires a constant
-      // value. But if we are initializing a value we don't know if it is a
-      // constexpr variable or not. We can check the EvaluatingDecl to determine
-      // if it constexpr or not. If not then we don't want to emit a diagnostic.
-      if (const auto *VD = dyn_cast_or_null<VarDecl>(
-              Info.EvaluatingDecl.dyn_cast<const ValueDecl *>()))
-        ConstexprVar = VD->isConstexpr();
-
+    if (Info.Ctx.getLangOpts().CPlusPlus && DestType->isEnumeralType()) {
       const EnumType *ET = dyn_cast<EnumType>(DestType.getCanonicalType());
       const EnumDecl *ED = ET->getDecl();
       // Check that the value is within the range of the enumeration values.
@@ -15257,13 +15243,13 @@ bool IntExprEvaluator::VisitCastExpr(const CastExpr *E) {
         ED->getValueRange(Max, Min);
         --Max;
 
-        if (ED->getNumNegativeBits() && ConstexprVar &&
+        if (ED->getNumNegativeBits() &&
             (Max.slt(Result.getInt().getSExtValue()) ||
              Min.sgt(Result.getInt().getSExtValue())))
           Info.CCEDiag(E, diag::note_constexpr_unscoped_enum_out_of_range)
               << llvm::toString(Result.getInt(), 10) << Min.getSExtValue()
               << Max.getSExtValue() << ED;
-        else if (!ED->getNumNegativeBits() && ConstexprVar &&
+        else if (!ED->getNumNegativeBits() &&
                  Max.ult(Result.getInt().getZExtValue()))
           Info.CCEDiag(E, diag::note_constexpr_unscoped_enum_out_of_range)
               << llvm::toString(Result.getInt(), 10) << Min.getZExtValue()
diff --git a/clang/test/SemaCXX/constant-expression-cxx11.cpp b/clang/test/SemaCXX/constant-expression-cxx11.cpp
index 0a135654fab18..10c514be5025f 100644
--- a/clang/test/SemaCXX/constant-expression-cxx11.cpp
+++ b/clang/test/SemaCXX/constant-expression-cxx11.cpp
@@ -2513,6 +2513,9 @@ void testValueInRangeOfEnumerationValues() {
   // expected-error@-1 {{constexpr variable 'x2' must be initialized by a constant expression}}
   // expected-note@-2 {{integer value 8 is outside the valid range of values [-8, 7] for the enumeration type 'E1'}}
   E1 x2b = static_cast<E1>(8); // ok, not a constant expression context
+  static_assert(static_cast<E1>(8));
+  // expected-error@-1 {{static assertion expression is not an integral constant expression}}
+  // expected-note@-2 {{integer value 8 is outside the valid range of values [-8, 7] for the enumeration type 'E1'}}
 
   constexpr E2 x3 = static_cast<E2>(-8);
   // expected-error@-1 {{constexpr variable 'x3' must be initialized by a constant expression}}
@@ -2551,6 +2554,10 @@ void testValueInRangeOfEnumerationValues() {
   // expected-note@-2 {{integer value 2147483648 is outside the valid range of values [-2147483648, 2147483647] for the enumeration type 'EMaxInt'}}
 
   const NumberType neg_one = (NumberType) ((NumberType) 0 - (NumberType) 1); // ok, not a constant expression context
+  constexpr NumberType neg_one_constexpr = neg_one;
+  // expected-error@-1 {{constexpr variable 'neg_one_constexpr' must be initialized by a constant expression}}
+  // expected-note@-2 {{initializer of 'neg_one' is not a constant expression}}
+  // expected-note@-4 {{declared here}}
 
   CONSTEXPR_CAST_TO_SYSTEM_ENUM_OUTSIDE_OF_RANGE;
   // expected-error@-1 {{constexpr variable 'system_enum' must be initialized by a constant expression}}

In 8de5137 and related patches, we
added some code to avoid triggering -Wenum-constexpr-conversion in some
cases.  This isn't necessary anymore because -Wenum-constexpr-conversion
doesn't exist anymore.  And the checks are subtly wrong: they exclude
cases where we actually do need to check the conversion. This patch gets
rid of the unnecessary checks.
@efriedma-quic efriedma-quic force-pushed the enum-conversion-nonvar branch from 416a167 to 189674e Compare June 6, 2025 00:59
@cor3ntin cor3ntin requested review from erichkeane and tbaederr June 6, 2025 07:46
@efriedma-quic efriedma-quic merged commit 6090232 into llvm:main Jun 6, 2025
7 checks passed
@@ -2513,6 +2513,9 @@ void testValueInRangeOfEnumerationValues() {
// expected-error@-1 {{constexpr variable 'x2' must be initialized by a constant expression}}
// expected-note@-2 {{integer value 8 is outside the valid range of values [-8, 7] for the enumeration type 'E1'}}
E1 x2b = static_cast<E1>(8); // ok, not a constant expression context
static_assert(static_cast<E1>(8), "");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess these are the cases my previous checks missed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. The static_assert case wasn't emitting an error because of the Info.EvalMode == EvalInfo::EM_ConstantExpression check. The "neg_one_constexpr" case wasn't emitting an error because of the ConstexprVar check.

(Looking again, I guess I could have also come up with a separate testcase for the InConstantContext check, but I didn't really think too deeply about it.)

rorth pushed a commit to rorth/llvm-project that referenced this pull request Jun 11, 2025
)

In 8de5137 and related patches, we
added some code to avoid triggering -Wenum-constexpr-conversion in some
cases. This isn't necessary anymore because -Wenum-constexpr-conversion
doesn't exist anymore. And the checks are subtly wrong: they exclude
cases where we actually do need to check the conversion. This patch gets
rid of the unnecessary checks.
DhruvSrivastavaX pushed a commit to DhruvSrivastavaX/lldb-for-aix that referenced this pull request Jun 12, 2025
)

In 8de5137 and related patches, we
added some code to avoid triggering -Wenum-constexpr-conversion in some
cases. This isn't necessary anymore because -Wenum-constexpr-conversion
doesn't exist anymore. And the checks are subtly wrong: they exclude
cases where we actually do need to check the conversion. This patch gets
rid of the unnecessary checks.
@rnk
Copy link
Collaborator

rnk commented Jun 13, 2025

Internally, we discovered that this patch breaks Boost as used by MySQL. I don't have a full external reproducer at the moment and I don't know whether the code is valid or not, but I wanted to give a heads up that we may need to revisit this. This seems like the kind of incompatibility that would get reported during release testing later on.

@wlei-llvm
Copy link
Contributor

Internally, we discovered that this patch breaks Boost as used by MySQL. I don't have a full external reproducer at the moment and I don't know whether the code is valid or not, but I wanted to give a heads up that we may need to revisit this. This seems like the kind of incompatibility that would get reported during release testing later on.

+1, this also breaks our internal Boost which is on 1.77.0. I'm checking out the latest Boost to verify if there is already a fix.

@rnk Do you know yet if it also breaks the latest Boost?

I found some related fixes in Boost that may help:
boostorg/numeric_conversion@50a1eae

percona/percona-server-mongodb@8e1167e

tomtor pushed a commit to tomtor/llvm-project that referenced this pull request Jun 14, 2025
)

In 8de5137 and related patches, we
added some code to avoid triggering -Wenum-constexpr-conversion in some
cases. This isn't necessary anymore because -Wenum-constexpr-conversion
doesn't exist anymore. And the checks are subtly wrong: they exclude
cases where we actually do need to check the conversion. This patch gets
rid of the unnecessary checks.
@efriedma-quic
Copy link
Collaborator Author

Most likely, the user code is invalid. I was hoping we would get away without hitting any bugs in major frameworks, but I'm not surprised we hit something.

If this is widespread, we can look into mitigations, but it's difficult to downgrade constant evaluation errors to warnings. (Particularly if SFINAE is involved.)

@eaeltsin
Copy link
Contributor

I think the pattern is something like

enum { const_value = 1 };
template<size_t N> class Foo {...};
Foo<const_value + 1> foo;

@efriedma-quic
Copy link
Collaborator Author

That example doesn't have any int->enum conversions.

@eaeltsin
Copy link
Contributor

eaeltsin commented Jun 16, 2025

Sorry, I over-generalized :)

Here is the proper sample - https://godbolt.org/z/E9xscz1os:

template< typename T, T N >
struct IC
{
  static const T next_value = static_cast<T>(N + 1);
  typedef IC< T, next_value > next;
};

enum E
{
  integral_to_integral,
  integral_to_float,
  float_to_integral,
  float_to_float
};

IC<E, float_to_float> foo;

@wlei-llvm
Copy link
Contributor

Internally, we discovered that this patch breaks Boost as used by MySQL. I don't have a full external reproducer at the moment and I don't know whether the code is valid or not, but I wanted to give a heads up that we may need to revisit this. This seems like the kind of incompatibility that would get reported during release testing later on.

+1, this also breaks our internal Boost which is on 1.77.0. I'm checking out the latest Boost to verify if there is already a fix.

@rnk Do you know yet if it also breaks the latest Boost?

I found some related fixes in Boost that may help: boostorg/numeric_conversion@50a1eae

percona/percona-server-mongodb@8e1167e

JFYI, backporting boostorg/numeric_conversion@50a1eae?fbclid=IwY2xjawK9JA1leHRuA2FlbQIxMQBicmlkETFwWjFtb3JJaWRrNWNSYlZXAR5AdfRqNL1nDOUfpxRcrFyszDejF-qEjXb_H0f82unnsTPFqOYsRiDlItWLRw_aem_dNRSalHTNq6FUW_hRNhJrQ
fixes our issue.

we got the same repro:

enum int_float_mixture_enum {
    integral_to_integral,
  };
  template <typename T, T N> struct integral_c {
    static const T value = N;
    static const T prior_value = T((N - 1));
    typedef integral_c<T, prior_value> prior;
  };
  integral_c<int_float_mixture_enum, integral_to_integral> a;

@jyknight
Copy link
Member

This same code in Boost was already implicated by the 2022 changes, and I reported a bug for it back then. It was fixed a couple years later in Boost 1.86. boostorg/mpl#69 (comment) mentions two commits that can be backported to older versions of Boost.

@rnk
Copy link
Collaborator

rnk commented Jun 16, 2025

If we've had fixes available for years, I think backporting them would be the right call, but this seems like it might be a release-note-worthy change.

@efriedma-quic
Copy link
Collaborator Author

That pattern look like basically the same thing as the neg_one_constexpr testcase I added. The diagnostic is expected.

Posted #144407 to add a relnote.

@eaeltsin
Copy link
Contributor

I'm not questioning the correctness of the change, just sharing the now-breaking pattern that is quite wide-spread.

We also hope to handle this internally by patching boost.

@erichkeane
Copy link
Collaborator

@efriedma-quic : If you haven't already, please add a release note that is pretty descriptive of the error/issue here, so that people can find it in the future. Additionally, a discourse post to the 'potentially-breaking' tag would be a VERY useful place to socialize this.

@efriedma-quic
Copy link
Collaborator Author

@erichkeane already added release note in #144407; please take a look to see if it makes sense.

@efriedma-quic
Copy link
Collaborator Author

This might not actually be fixed in the latest boost::mpl. See https://godbolt.org/z/jaxqTK59a . Maybe someone else can confirm I'm not missing anything?

If that's the case, we might need to downgrade the error to a warning somehow. Probably we can avoid any interaction with SFINAE by adding a hack to VarDecl::checkForConstantInitialization.

@erichkeane
Copy link
Collaborator

@erichkeane already added release note in #144407; please take a look to see if it makes sense.

Yeah, sorry, going backwards through my reviews after my week at WG21 :) I commented there, but generally 'good enough'. The discourse post would be appreciated if you haven't already.

@jyknight
Copy link
Member

This might not actually be fixed in the latest boost::mpl.

Hm, indeed. It seems that the "trick" they used to solve it in mpl indeed was fixed/obsoleted by this change -- the trick no longer avoids the error. Sorry, I didn't realize that their fix was depending on a bug in Clang!

However -- I haven't actually seen any problems caused by this commit, when using an up-to-date version of Boost! I believe that's because just about nothing actually uses boost::mpl::integral_c. The boost fix which actually matters to real-world code was that boost numeric_conversion was changed to stop using boost::mpl::integral_c. And that fix is still operational!

So...I wonder if it maybe does not really matter that MPL's integral_c is broken again, if it's not really impacting anything. It'd be great if some expert could help them craft a correct fix to the type, of course...

efriedma-quic added a commit to efriedma-quic/llvm-project that referenced this pull request Jun 25, 2025
Include more specifics from recent discussion on llvm#143034.
@efriedma-quic
Copy link
Collaborator Author

Pushed #145755 to revise the release note with information about numeric_conversion specifically. If the underlying issue with boost::mpl::integral_c isn't actually impacting anyone, I won't worry about it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants