forked from OSchip/llvm-project
[Clang] Diagnose ill-formed constant expression when setting a non fixed enum to a value outside the range of the enumeration values
DR2338 clarified that it was undefined behavior to set the value outside the range of the enumerations values for an enum without a fixed underlying type. We should diagnose this with a constant expression context. Differential Revision: https://reviews.llvm.org/D130058
This commit is contained in:
parent
58526b2d2b
commit
b364535304
|
@ -56,6 +56,10 @@ Bug Fixes
|
||||||
|
|
||||||
Improvements to Clang's diagnostics
|
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: <https://github.com/llvm/llvm-project/issues/50055>`_.
|
||||||
|
|
||||||
Non-comprehensive list of changes in this release
|
Non-comprehensive list of changes in this release
|
||||||
-------------------------------------------------
|
-------------------------------------------------
|
||||||
|
|
|
@ -3842,6 +3842,11 @@ public:
|
||||||
/// -101 1001011 8
|
/// -101 1001011 8
|
||||||
unsigned getNumNegativeBits() const { return EnumDeclBits.NumNegativeBits; }
|
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.
|
/// Returns true if this is a C++11 scoped enumeration.
|
||||||
bool isScoped() const { return EnumDeclBits.IsScoped; }
|
bool isScoped() const { return EnumDeclBits.IsScoped; }
|
||||||
|
|
||||||
|
|
|
@ -366,6 +366,9 @@ def note_constexpr_unsupported_layout : Note<
|
||||||
"type %0 has unexpected layout">;
|
"type %0 has unexpected layout">;
|
||||||
def note_constexpr_unsupported_flexible_array : Note<
|
def note_constexpr_unsupported_flexible_array : Note<
|
||||||
"flexible array initialization is not yet supported">;
|
"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<
|
def err_experimental_clang_interp_failed : Error<
|
||||||
"the experimental clang interpreter failed to evaluate an expression">;
|
"the experimental clang interpreter failed to evaluate an expression">;
|
||||||
|
|
||||||
|
|
|
@ -4621,6 +4621,21 @@ SourceRange EnumDecl::getSourceRange() const {
|
||||||
return Res;
|
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
|
// RecordDecl Implementation
|
||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
|
@ -13519,6 +13519,37 @@ bool IntExprEvaluator::VisitCastExpr(const CastExpr *E) {
|
||||||
return Info.Ctx.getTypeSize(DestType) == Info.Ctx.getTypeSize(SrcType);
|
return Info.Ctx.getTypeSize(DestType) == Info.Ctx.getTypeSize(SrcType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (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.
|
||||||
|
//
|
||||||
|
// 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,
|
return Success(HandleIntToIntCast(Info, E, DestType, SrcType,
|
||||||
Result.getInt()), E);
|
Result.getInt()), E);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1661,21 +1661,7 @@ static bool getRangeForType(CodeGenFunction &CGF, QualType Ty,
|
||||||
End = llvm::APInt(CGF.getContext().getTypeSize(Ty), 2);
|
End = llvm::APInt(CGF.getContext().getTypeSize(Ty), 2);
|
||||||
} else {
|
} else {
|
||||||
const EnumDecl *ED = ET->getDecl();
|
const EnumDecl *ED = ET->getDecl();
|
||||||
llvm::Type *LTy = CGF.ConvertTypeForMem(ED->getIntegerType());
|
ED->getValueRange(End, Min);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -223,7 +223,7 @@ namespace NoReturnSingleSuccessor {
|
||||||
// CHECK-NEXT: Succs (1): B1
|
// CHECK-NEXT: Succs (1): B1
|
||||||
// CHECK: [B0 (EXIT)]
|
// CHECK: [B0 (EXIT)]
|
||||||
// CHECK-NEXT: Preds (1): B1
|
// CHECK-NEXT: Preds (1): B1
|
||||||
enum MyEnum { A, B, C };
|
enum MyEnum : int { A, B, C };
|
||||||
static const enum MyEnum D = (enum MyEnum) 32;
|
static const enum MyEnum D = (enum MyEnum) 32;
|
||||||
|
|
||||||
int test_enum_with_extension(enum MyEnum value) {
|
int test_enum_with_extension(enum MyEnum value) {
|
||||||
|
|
|
@ -206,19 +206,19 @@ void test_range_0_13(svbool_t pg, const void *const_void_ptr)
|
||||||
{
|
{
|
||||||
// expected-error-re@+1 {{argument value {{[0-9]+}} is outside the valid range [0, 13]}}
|
// expected-error-re@+1 {{argument value {{[0-9]+}} is outside the valid range [0, 13]}}
|
||||||
svprfb(pg, const_void_ptr, svprfop(14));
|
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));
|
svprfb_vnum(pg, const_void_ptr, 0, svprfop(-1));
|
||||||
// expected-error-re@+1 {{argument value {{[0-9]+}} is outside the valid range [0, 13]}}
|
// expected-error-re@+1 {{argument value {{[0-9]+}} is outside the valid range [0, 13]}}
|
||||||
svprfd(pg, const_void_ptr, svprfop(14));
|
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));
|
svprfd_vnum(pg, const_void_ptr, 0, svprfop(-1));
|
||||||
// expected-error-re@+1 {{argument value {{[0-9]+}} is outside the valid range [0, 13]}}
|
// expected-error-re@+1 {{argument value {{[0-9]+}} is outside the valid range [0, 13]}}
|
||||||
svprfh(pg, const_void_ptr, svprfop(14));
|
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));
|
svprfh_vnum(pg, const_void_ptr, 0, svprfop(-1));
|
||||||
// expected-error-re@+1 {{argument value {{[0-9]+}} is outside the valid range [0, 13]}}
|
// expected-error-re@+1 {{argument value {{[0-9]+}} is outside the valid range [0, 13]}}
|
||||||
svprfw(pg, const_void_ptr, svprfop(14));
|
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));
|
svprfw_vnum(pg, const_void_ptr, 0, svprfop(-1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2399,3 +2399,50 @@ void local_constexpr_var() {
|
||||||
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 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}}
|
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<E1>(-8);
|
||||||
|
constexpr E1 x2 = static_cast<E1>(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<E2>(-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<E2>(0);
|
||||||
|
constexpr E2 x5 = static_cast<E2>(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<E3>(-2048);
|
||||||
|
constexpr E3 x7 = static_cast<E3>(-8);
|
||||||
|
constexpr E3 x8 = static_cast<E3>(0);
|
||||||
|
constexpr E3 x9 = static_cast<E3>(8);
|
||||||
|
constexpr E3 x10 = static_cast<E3>(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<E4>(0);
|
||||||
|
constexpr E4 x12 = static_cast<E4>(1);
|
||||||
|
constexpr E4 x13 = static_cast<E4>(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<EEmpty>(0);
|
||||||
|
constexpr EEmpty x15 = static_cast<EEmpty>(1);
|
||||||
|
constexpr EEmpty x16 = static_cast<EEmpty>(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<EFixed>(100);
|
||||||
|
constexpr EScoped x18 = static_cast<EScoped>(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -327,7 +327,7 @@ namespace test11 {
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace PR35586 {
|
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.
|
enum B { F = (enum C) -1, T}; // this should compile cleanly, it used to assert.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
namespace NamedEnumNS
|
namespace NamedEnumNS
|
||||||
{
|
{
|
||||||
|
|
||||||
enum NamedEnum
|
enum class NamedEnum
|
||||||
{
|
{
|
||||||
Val0,
|
Val0,
|
||||||
Val1
|
Val1
|
||||||
|
@ -13,9 +13,9 @@ template <NamedEnum E>
|
||||||
void foo();
|
void foo();
|
||||||
|
|
||||||
void test() {
|
void test() {
|
||||||
// CHECK: template<> void foo<NamedEnumNS::Val0>()
|
// CHECK: template<> void foo<NamedEnumNS::NamedEnum::Val0>()
|
||||||
NamedEnumNS::foo<Val0>();
|
NamedEnumNS::foo<NamedEnum::Val0>();
|
||||||
// CHECK: template<> void foo<NamedEnumNS::Val1>()
|
// CHECK: template<> void foo<NamedEnumNS::NamedEnum::Val1>()
|
||||||
NamedEnumNS::foo<(NamedEnum)1>();
|
NamedEnumNS::foo<(NamedEnum)1>();
|
||||||
// CHECK: template<> void foo<(NamedEnumNS::NamedEnum)2>()
|
// CHECK: template<> void foo<(NamedEnumNS::NamedEnum)2>()
|
||||||
NamedEnumNS::foo<(NamedEnum)2>();
|
NamedEnumNS::foo<(NamedEnum)2>();
|
||||||
|
|
|
@ -13842,7 +13842,7 @@ and <I>POD class</I></td>
|
||||||
<td><a href="https://wg21.link/cwg2338">2338</a></td>
|
<td><a href="https://wg21.link/cwg2338">2338</a></td>
|
||||||
<td>CD5</td>
|
<td>CD5</td>
|
||||||
<td>Undefined behavior converting to short enums with fixed underlying types</td>
|
<td>Undefined behavior converting to short enums with fixed underlying types</td>
|
||||||
<td class="full" align="center">Clang 12</td>
|
<td class="full" align="center">Clang 16</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr id="2339">
|
<tr id="2339">
|
||||||
<td><a href="https://wg21.link/cwg2339">2339</a></td>
|
<td><a href="https://wg21.link/cwg2339">2339</a></td>
|
||||||
|
|
|
@ -25,9 +25,9 @@ struct udt { };
|
||||||
}
|
}
|
||||||
template<template<typename> class T>
|
template<template<typename> class T>
|
||||||
void ttp_user() { }
|
void ttp_user() { }
|
||||||
enum Enumeration { Enumerator1, Enumerator2, Enumerator3 = 1 };
|
enum Enumeration : int { Enumerator1, Enumerator2, Enumerator3 = 1 };
|
||||||
enum class EnumerationClass { 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 };
|
enum EnumerationSmall : unsigned char { kNeg = 0xff };
|
||||||
}
|
}
|
||||||
template <typename... Ts>
|
template <typename... Ts>
|
||||||
|
|
Loading…
Reference in New Issue