Index: clang/docs/ReleaseNotes.rst =================================================================== --- clang/docs/ReleaseNotes.rst +++ clang/docs/ReleaseNotes.rst @@ -52,6 +52,10 @@ Improvements to Clang's diagnostics ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +- Clang will now correctly diagnose as ill-formed a constant expression where an + enum without a fixed underlying type is set to a value outside the range of + of the enumerations values. Fixes + `Issue 50055: `_. Non-comprehensive list of changes in this release ------------------------------------------------- Index: clang/include/clang/AST/Decl.h =================================================================== --- clang/include/clang/AST/Decl.h +++ clang/include/clang/AST/Decl.h @@ -3842,6 +3842,11 @@ /// -101 1001011 8 unsigned getNumNegativeBits() const { return EnumDeclBits.NumNegativeBits; } + /// Calculates the [Min,Max) values the enum can store based on the + /// NumPositiveBits and NumNegativeBits. This matters for enums that do not + /// have a fixed underlying type. + void getValueRange(llvm::APInt &Max, llvm::APInt &Min) const; + /// Returns true if this is a C++11 scoped enumeration. bool isScoped() const { return EnumDeclBits.IsScoped; } Index: clang/include/clang/Basic/DiagnosticASTKinds.td =================================================================== --- clang/include/clang/Basic/DiagnosticASTKinds.td +++ clang/include/clang/Basic/DiagnosticASTKinds.td @@ -366,6 +366,8 @@ "type %0 has unexpected layout">; def note_constexpr_unsupported_flexible_array : Note< "flexible array initialization is not yet supported">; +def note_constexpr_unscoped_enum_out_of_range : Note< + "integer value %0 is outside the valid range of values [%1, %2) for this enumeration type">; def err_experimental_clang_interp_failed : Error< "the experimental clang interpreter failed to evaluate an expression">; Index: clang/lib/AST/Decl.cpp =================================================================== --- clang/lib/AST/Decl.cpp +++ clang/lib/AST/Decl.cpp @@ -4621,6 +4621,21 @@ return Res; } +void EnumDecl::getValueRange(llvm::APInt &Max, llvm::APInt &Min) const { + unsigned Bitwidth = getASTContext().getIntWidth(getIntegerType()); + unsigned NumNegativeBits = getNumNegativeBits(); + unsigned NumPositiveBits = getNumPositiveBits(); + + if (NumNegativeBits) { + unsigned NumBits = std::max(NumNegativeBits, NumPositiveBits + 1); + Max = llvm::APInt(Bitwidth, 1) << (NumBits - 1); + Min = -Max; + } else { + Max = llvm::APInt(Bitwidth, 1) << NumPositiveBits; + Min = llvm::APInt::getZero(Bitwidth); + } +} + //===----------------------------------------------------------------------===// // RecordDecl Implementation //===----------------------------------------------------------------------===// Index: clang/lib/AST/ExprConstant.cpp =================================================================== --- clang/lib/AST/ExprConstant.cpp +++ clang/lib/AST/ExprConstant.cpp @@ -13519,6 +13519,37 @@ return Info.Ctx.getTypeSize(DestType) == Info.Ctx.getTypeSize(SrcType); } + if (DestType->isEnumeralType()) { + const EnumType *ET = dyn_cast(DestType.getCanonicalType()); + const EnumDecl *ED = ET->getDecl(); + // Check that the value is within the range of the enumeration values. + // + // This corressponds to [expr.static.cast]p10 which says: + // A value of integral or enumeration type can be explicitly converted + // to a complete enumeration type ... If the 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 ([dcl.enum]), and + // otherwise, the behavior is undefined. + // + // This was resolved as part of DR2338 which has CD5 status. + if (!ED->isFixed()) { + llvm::APInt Min; + llvm::APInt Max; + + ED->getValueRange(Max, Min); + + if (ED->getNumNegativeBits() && + (Max.sle(Result.getInt().getSExtValue()) || + Min.sgt(Result.getInt().getSExtValue()))) + CCEDiag(E, diag::note_constexpr_unscoped_enum_out_of_range) + << Result.getInt() << Min.getSExtValue() << Max.getSExtValue(); + else if (!ED->getNumNegativeBits() && + Max.ule(Result.getInt().getZExtValue())) + CCEDiag(E, diag::note_constexpr_unscoped_enum_out_of_range) + << Result.getInt() << Min.getZExtValue() << Max.getZExtValue(); + } + } + return Success(HandleIntToIntCast(Info, E, DestType, SrcType, Result.getInt()), E); } Index: clang/lib/CodeGen/CGExpr.cpp =================================================================== --- clang/lib/CodeGen/CGExpr.cpp +++ clang/lib/CodeGen/CGExpr.cpp @@ -1661,21 +1661,7 @@ End = llvm::APInt(CGF.getContext().getTypeSize(Ty), 2); } else { const EnumDecl *ED = ET->getDecl(); - llvm::Type *LTy = CGF.ConvertTypeForMem(ED->getIntegerType()); - unsigned Bitwidth = LTy->getScalarSizeInBits(); - unsigned NumNegativeBits = ED->getNumNegativeBits(); - unsigned NumPositiveBits = ED->getNumPositiveBits(); - - if (NumNegativeBits) { - unsigned NumBits = std::max(NumNegativeBits, NumPositiveBits + 1); - assert(NumBits <= Bitwidth); - End = llvm::APInt(Bitwidth, 1) << (NumBits - 1); - Min = -End; - } else { - assert(NumPositiveBits <= Bitwidth); - End = llvm::APInt(Bitwidth, 1) << NumPositiveBits; - Min = llvm::APInt::getZero(Bitwidth); - } + ED->getValueRange(End, Min); } return true; } Index: clang/test/Analysis/cfg.cpp =================================================================== --- clang/test/Analysis/cfg.cpp +++ clang/test/Analysis/cfg.cpp @@ -223,7 +223,7 @@ // CHECK-NEXT: Succs (1): B1 // CHECK: [B0 (EXIT)] // CHECK-NEXT: Preds (1): B1 -enum MyEnum { A, B, C }; +enum MyEnum : int { A, B, C }; static const enum MyEnum D = (enum MyEnum) 32; int test_enum_with_extension(enum MyEnum value) { Index: clang/test/Sema/aarch64-sve-intrinsics/acle_sve_imm.cpp =================================================================== --- clang/test/Sema/aarch64-sve-intrinsics/acle_sve_imm.cpp +++ clang/test/Sema/aarch64-sve-intrinsics/acle_sve_imm.cpp @@ -206,19 +206,19 @@ { // expected-error-re@+1 {{argument value {{[0-9]+}} is outside the valid range [0, 13]}} svprfb(pg, const_void_ptr, svprfop(14)); - // expected-error-re@+1 {{argument value {{[0-9]+}} is outside the valid range [0, 13]}} + // expected-error-re@+1 {{must be a constant integer}} svprfb_vnum(pg, const_void_ptr, 0, svprfop(-1)); // expected-error-re@+1 {{argument value {{[0-9]+}} is outside the valid range [0, 13]}} svprfd(pg, const_void_ptr, svprfop(14)); - // expected-error-re@+1 {{argument value {{[0-9]+}} is outside the valid range [0, 13]}} + // expected-error-re@+1 {{must be a constant integer}} svprfd_vnum(pg, const_void_ptr, 0, svprfop(-1)); // expected-error-re@+1 {{argument value {{[0-9]+}} is outside the valid range [0, 13]}} svprfh(pg, const_void_ptr, svprfop(14)); - // expected-error-re@+1 {{argument value {{[0-9]+}} is outside the valid range [0, 13]}} + // expected-error-re@+1 {{must be a constant integer}} svprfh_vnum(pg, const_void_ptr, 0, svprfop(-1)); // expected-error-re@+1 {{argument value {{[0-9]+}} is outside the valid range [0, 13]}} svprfw(pg, const_void_ptr, svprfop(14)); - // expected-error-re@+1 {{argument value {{[0-9]+}} is outside the valid range [0, 13]}} + // expected-error-re@+1 {{must be a constant integer}} svprfw_vnum(pg, const_void_ptr, 0, svprfop(-1)); } Index: clang/test/SemaCXX/constant-expression-cxx11.cpp =================================================================== --- clang/test/SemaCXX/constant-expression-cxx11.cpp +++ clang/test/SemaCXX/constant-expression-cxx11.cpp @@ -2399,3 +2399,50 @@ constexpr int a = 0; // expected-note {{address of non-static constexpr variable 'a' may differ on each invocation of the enclosing function; add 'static' to give it a constant address}} constexpr const int *p = &a; // expected-error {{constant expression}} expected-note {{pointer to 'a' is not a constant expression}} } + +namespace GH50055 { +// Enums without fixed underlying type +enum E1 {e11=-4, e12=4}; +enum E2 {e21=0, e22=4}; +enum E3 {e31=-4, e32=1024}; +enum E4 {e41=0}; +// Empty but as-if it had a single enumerator with value 0 +enum EEmpty {}; + +// Enum with fixed underlying type because the underlying type is explicitly specified +enum EFixed : int {efixed1=-4, efixed2=4}; +// Enum with fixed underlying type because it is scoped +enum class EScoped {escoped1=-4, escoped2=4}; + +void testValueInRangeOfEnumerationValues() { + constexpr E1 x1 = static_cast(-8); + constexpr E1 x2 = static_cast(8); // expected-error {{must be initialized by a constant expression}} + // expected-note@-1 {{integer value 8 is outside the valid range of values [-8, 8) for this enumeration type}} + + constexpr E2 x3 = static_cast(-8); // expected-error {{must be initialized by a constant expression}} + // expected-note@-1 {{integer value -8 is outside the valid range of values [0, 8) for this enumeration type}} + constexpr E2 x4 = static_cast(0); + constexpr E2 x5 = static_cast(8); // expected-error {{must be initialized by a constant expression}} + // expected-note@-1 {{integer value 8 is outside the valid range of values [0, 8) for this enumeration type}} + + constexpr E3 x6 = static_cast(-2048); + constexpr E3 x7 = static_cast(-8); + constexpr E3 x8 = static_cast(0); + constexpr E3 x9 = static_cast(8); + constexpr E3 x10 = static_cast(2048); // expected-error {{must be initialized by a constant expression}} + // expected-note@-1 {{integer value 2048 is outside the valid range of values [-2048, 2048) for this enumeration type}} + + constexpr E4 x11 = static_cast(0); + constexpr E4 x12 = static_cast(1); + constexpr E4 x13 = static_cast(2); // expected-error {{must be initialized by a constant expression}} + // expected-note@-1 {{integer value 2 is outside the valid range of values [0, 2) for this enumeration type}} + + constexpr EEmpty x14 = static_cast(0); + constexpr EEmpty x15 = static_cast(1); + constexpr EEmpty x16 = static_cast(2); // expected-error {{must be initialized by a constant expression}} + // expected-note@-1 {{integer value 2 is outside the valid range of values [0, 2) for this enumeration type}} + + constexpr EFixed x17 = static_cast(100); + constexpr EScoped x18 = static_cast(100); +} +} Index: clang/test/SemaCXX/enum-scoped.cpp =================================================================== --- clang/test/SemaCXX/enum-scoped.cpp +++ clang/test/SemaCXX/enum-scoped.cpp @@ -327,7 +327,7 @@ } namespace PR35586 { - enum C { R, G, B }; + enum C { R=-1, G, B }; enum B { F = (enum C) -1, T}; // this should compile cleanly, it used to assert. }; Index: clang/test/SemaTemplate/temp_arg_enum_printing.cpp =================================================================== --- clang/test/SemaTemplate/temp_arg_enum_printing.cpp +++ clang/test/SemaTemplate/temp_arg_enum_printing.cpp @@ -3,7 +3,7 @@ namespace NamedEnumNS { -enum NamedEnum +enum class NamedEnum { Val0, Val1 @@ -13,9 +13,9 @@ void foo(); void test() { - // CHECK: template<> void foo() - NamedEnumNS::foo(); - // CHECK: template<> void foo() + // CHECK: template<> void foo() + NamedEnumNS::foo(); + // CHECK: template<> void foo() NamedEnumNS::foo<(NamedEnum)1>(); // CHECK: template<> void foo<(NamedEnumNS::NamedEnum)2>() NamedEnumNS::foo<(NamedEnum)2>(); Index: clang/www/cxx_dr_status.html =================================================================== --- clang/www/cxx_dr_status.html +++ clang/www/cxx_dr_status.html @@ -13842,7 +13842,7 @@ 2338 CD5 Undefined behavior converting to short enums with fixed underlying types - Clang 12 + Clang 16 2339 Index: cross-project-tests/debuginfo-tests/clang_llvm_roundtrip/simplified_template_names.cpp =================================================================== --- cross-project-tests/debuginfo-tests/clang_llvm_roundtrip/simplified_template_names.cpp +++ cross-project-tests/debuginfo-tests/clang_llvm_roundtrip/simplified_template_names.cpp @@ -25,9 +25,9 @@ } template class T> void ttp_user() { } -enum Enumeration { Enumerator1, Enumerator2, Enumerator3 = 1 }; +enum Enumeration : int { Enumerator1, Enumerator2, Enumerator3 = 1 }; enum class EnumerationClass { Enumerator1, Enumerator2, Enumerator3 = 1 }; -enum { AnonEnum1, AnonEnum2, AnonEnum3 = 1 }; +enum : int { AnonEnum1, AnonEnum2, AnonEnum3 = 1 }; enum EnumerationSmall : unsigned char { kNeg = 0xff }; } template