[clang][sema] Print more information about failed static assertions

For failed static assertions, try to take the expression apart and print
useful information about why it failed. In particular, look at binary
operators and print the compile-time evaluated value of the LHS/RHS.

Differential Revision: https://reviews.llvm.org/D130894
This commit is contained in:
Timm Bäder 2022-08-01 14:12:32 +02:00
parent 2813151730
commit 09117b2189
18 changed files with 223 additions and 18 deletions

View File

@ -96,6 +96,9 @@ Improvements to Clang's diagnostics
missing when used, or vice versa. This makes sure that Clang picks the
correct one, where it previously would consider multiple ones as potentially
acceptable (and erroneously use whichever one is tried first).
- Clang will now print more information about failed static assertions. In
particular, simple static assertion expressions are evaluated to their
compile-time value and printed out if the assertion fails.
Non-comprehensive list of changes in this release
-------------------------------------------------

View File

@ -1532,6 +1532,8 @@ def err_constexpr_if_condition_expression_is_not_constant : Error<
def err_static_assert_failed : Error<"static assertion failed%select{: %1|}0">;
def err_static_assert_requirement_failed : Error<
"static assertion failed due to requirement '%0'%select{: %2|}1">;
def note_expr_evaluates_to : Note<
"expression evaluates to '%0 %1 %2'">;
def warn_consteval_if_always_true : Warning<
"consteval if is always true in an %select{unevaluated|immediate}0 context">,

View File

@ -7480,6 +7480,7 @@ public:
StringLiteral *AssertMessageExpr,
SourceLocation RParenLoc,
bool Failed);
void DiagnoseStaticAssertDetails(const Expr *E);
FriendDecl *CheckFriendTypeDecl(SourceLocation LocStart,
SourceLocation FriendLoc,

View File

@ -16554,6 +16554,137 @@ Decl *Sema::ActOnStaticAssertDeclaration(SourceLocation StaticAssertLoc,
AssertMessage, RParenLoc, false);
}
/// Convert \V to a string we can present to the user in a diagnostic
/// \T is the type of the expression that has been evaluated into \V
static bool ConvertAPValueToString(const APValue &V, QualType T,
SmallVectorImpl<char> &Str) {
if (!V.hasValue())
return false;
switch (V.getKind()) {
case APValue::ValueKind::Int:
if (T->isBooleanType()) {
// Bools are reduced to ints during evaluation, but for
// diagnostic purposes we want to print them as
// true or false.
int64_t BoolValue = V.getInt().getExtValue();
assert((BoolValue == 0 || BoolValue == 1) &&
"Bool type, but value is not 0 or 1");
llvm::raw_svector_ostream OS(Str);
OS << (BoolValue ? "true" : "false");
} else if (T->isCharType()) {
// Same is true for chars.
Str.push_back('\'');
Str.push_back(V.getInt().getExtValue());
Str.push_back('\'');
} else
V.getInt().toString(Str);
break;
case APValue::ValueKind::Float:
V.getFloat().toString(Str);
break;
case APValue::ValueKind::LValue:
if (V.isNullPointer()) {
llvm::raw_svector_ostream OS(Str);
OS << "nullptr";
} else
return false;
break;
case APValue::ValueKind::ComplexFloat: {
llvm::raw_svector_ostream OS(Str);
OS << '(';
V.getComplexFloatReal().toString(Str);
OS << " + ";
V.getComplexFloatImag().toString(Str);
OS << "i)";
} break;
case APValue::ValueKind::ComplexInt: {
llvm::raw_svector_ostream OS(Str);
OS << '(';
V.getComplexIntReal().toString(Str);
OS << " + ";
V.getComplexIntImag().toString(Str);
OS << "i)";
} break;
default:
return false;
}
return true;
}
/// Some Expression types are not useful to print notes about,
/// e.g. literals and values that have already been expanded
/// before such as int-valued template parameters.
static bool UsefulToPrintExpr(const Expr *E) {
E = E->IgnoreParenImpCasts();
// Literals are pretty easy for humans to understand.
if (isa<IntegerLiteral, FloatingLiteral, CharacterLiteral, CXXBoolLiteralExpr,
CXXNullPtrLiteralExpr, FixedPointLiteral, ImaginaryLiteral>(E))
return false;
// These have been substituted from template parameters
// and appear as literals in the static assert error.
if (isa<SubstNonTypeTemplateParmExpr>(E))
return false;
// -5 is also simple to understand.
if (const auto *UnaryOp = dyn_cast_or_null<UnaryOperator>(E))
return UsefulToPrintExpr(UnaryOp->getSubExpr());
// Ignore nested binary operators. This could be a FIXME for improvements
// to the diagnostics in the future.
if (isa<BinaryOperator>(E))
return false;
return true;
}
/// Try to print more useful information about a failed static_assert
/// with expression \E
void Sema::DiagnoseStaticAssertDetails(const Expr *E) {
if (const auto *Op = dyn_cast_or_null<BinaryOperator>(E)) {
const Expr *LHS = Op->getLHS()->IgnoreParenImpCasts();
const Expr *RHS = Op->getRHS()->IgnoreParenImpCasts();
// Ignore comparisons of boolean expressions with a boolean literal.
if ((isa<CXXBoolLiteralExpr>(LHS) && RHS->getType()->isBooleanType()) ||
(isa<CXXBoolLiteralExpr>(RHS) && LHS->getType()->isBooleanType()))
return;
// Don't print obvious expressions.
if (!UsefulToPrintExpr(LHS) && !UsefulToPrintExpr(RHS))
return;
struct {
const Expr *Expr;
Expr::EvalResult Result;
SmallString<12> ValueString;
bool Print;
} DiagSide[2] = {{LHS, Expr::EvalResult(), {}, false},
{RHS, Expr::EvalResult(), {}, false}};
for (unsigned I = 0; I < 2; I++) {
const Expr *Side = DiagSide[I].Expr;
Side->EvaluateAsRValue(DiagSide[I].Result, Context, true);
DiagSide[I].Print = ConvertAPValueToString(
DiagSide[I].Result.Val, Side->getType(), DiagSide[I].ValueString);
}
if (DiagSide[0].Print && DiagSide[1].Print) {
Diag(Op->getExprLoc(), diag::note_expr_evaluates_to)
<< DiagSide[0].ValueString << Op->getOpcodeStr()
<< DiagSide[1].ValueString << Op->getSourceRange();
}
}
}
Decl *Sema::BuildStaticAssertDeclaration(SourceLocation StaticAssertLoc,
Expr *AssertExpr,
StringLiteral *AssertMessage,
@ -16612,6 +16743,7 @@ Decl *Sema::BuildStaticAssertDeclaration(SourceLocation StaticAssertLoc,
Diag(StaticAssertLoc, diag::err_static_assert_requirement_failed)
<< InnerCondDescription << !AssertMessage
<< Msg.str() << InnerCond->getSourceRange();
DiagnoseStaticAssertDetails(InnerCond);
} else {
Diag(StaticAssertLoc, diag::err_static_assert_failed)
<< !AssertMessage << Msg.str() << AssertExpr->getSourceRange();

View File

@ -98,7 +98,8 @@ namespace dependent {
static_assert(sizeof(arr2) == 12, "");
// Use a failing test to ensure the type isn't considered dependent.
static_assert(sizeof(arr2) == 13, ""); // expected-error {{failed}}
static_assert(sizeof(arr2) == 13, ""); // expected-error {{failed}} \
// expected-note {{evaluates to '12 == 13'}}
}
void g() { f<int[3]>(); } // expected-note {{in instantiation of}}

View File

@ -178,7 +178,8 @@ namespace dr727 { // dr727: partial
static_assert(B<0>().v<1> == 3, "");
static_assert(B<0>().v<0> == 4, "");
#if __cplusplus < 201702L
// expected-error@-2 {{failed}}
// expected-error@-2 {{failed}} \
// expected-note@-2 {{evaluates to '2 == 4'}}
#endif
static_assert(B<1>().w<1> == 1, "");

View File

@ -40,8 +40,10 @@ template<typename ...T> void f(T ...t) {
template<int ...a> constexpr auto x = [...z = a] (auto F) { return F(z...); };
static_assert(x<1,2,3>([](int a, int b, int c) { return 100 * a + 10 * b + c; }) == 123);
static_assert(x<1,2,3>([](int a, int b, int c) { return 100 * a + 10 * b + c; }) == 124); // expected-error {{failed}}
static_assert(x<1,2,3>([](int a, int b, int c) { return 100 * a + 10 * b + c; }) == 124); // expected-error {{failed}} \
// expected-note {{evaluates to '123 == 124'}}
template<int ...a> constexpr auto y = [z = a...] (auto F) { return F(z...); }; // expected-error {{must appear before the name of the capture}}
static_assert(y<1,2,3>([](int a, int b, int c) { return 100 * a + 10 * b + c; }) == 123);
static_assert(y<1,2,3>([](int a, int b, int c) { return 100 * a + 10 * b + c; }) == 124); // expected-error {{failed}}
static_assert(y<1,2,3>([](int a, int b, int c) { return 100 * a + 10 * b + c; }) == 124); // expected-error {{failed}} \
// expected-note {{evaluates to '123 == 124'}}

View File

@ -21,7 +21,7 @@ error here;
#if !ENABLED_TRIGRAPHS
// expected-error@11 {{}} expected-warning@11 {{trigraph ignored}}
// expected-error@13 {{failed}} expected-warning@13 {{trigraph ignored}}
// expected-error@13 {{failed}} expected-warning@13 {{trigraph ignored}} expected-note@13 {{evaluates to ''?' == '#''}}
// expected-error@16 {{}}
// expected-error@20 {{}}
#else

View File

@ -167,7 +167,8 @@ namespace DependentMemberExpr {
// This used to mark 'f' invalid without producing any diagnostic. That's a
// little hard to detect, but we can make sure that constexpr evaluation
// fails when it should.
static_assert(A<int>().f() == 1); // expected-error {{static assertion failed}}
static_assert(A<int>().f() == 1); // expected-error {{static assertion failed}} \
// expected-note {{evaluates to '0 == 1'}}
#endif
}

View File

@ -26,7 +26,8 @@
static_assert(a, ""); // expected-error {{static assertion expression is not an integral constant expression}}
static_assert(sizeof(a) == 4, "");
static_assert(sizeof(a) == 3, ""); // expected-error {{static assertion failed}}
static_assert(sizeof(a) == 3, ""); // expected-error {{static assertion failed}} \
// expected-note {{evaluates to '4 == 3'}}
}
static_assert(1, "");
@ -40,7 +41,8 @@ _Static_assert(1, "");
static_assert(1, "");
_Static_assert(1, "");
static_assert(sizeof(b) == 4, "");
static_assert(sizeof(b) == 3, ""); // expected-error {{static assertion failed}}
static_assert(sizeof(b) == 3, ""); // expected-error {{static assertion failed}} \
// expected-note {{evaluates to '4 == 3'}}
}
static_assert(1, "");
@ -56,7 +58,8 @@ static_assert(1, "");
@interface B () {
int b;
static_assert(sizeof(b) == 4, "");
static_assert(sizeof(b) == 3, ""); // expected-error {{static assertion failed}}
static_assert(sizeof(b) == 3, ""); // expected-error {{static assertion failed}} \
// expected-note {{evaluates to '4 == 3'}}
}
@end

View File

@ -55,6 +55,7 @@ struct A {
typedef UNION(unsigned, struct A) U1; // ext-warning 3 {{'_Static_assert' is a C11 extension}}
UNION(char[2], short) u2 = { .one = { 'a', 'b' } }; // ext-warning 3 {{'_Static_assert' is a C11 extension}} cxx-warning {{designated initializers are a C++20 extension}}
typedef UNION(char, short) U3; // expected-error {{static assertion failed due to requirement 'sizeof(char) == sizeof(short)': type size mismatch}} \
// expected-note{{evaluates to '1 == 2'}} \
// ext-warning 3 {{'_Static_assert' is a C11 extension}}
typedef UNION(float, 0.5f) U4; // expected-error {{expected a type}} \
// ext-warning 3 {{'_Static_assert' is a C11 extension}}

View File

@ -1913,11 +1913,13 @@ namespace VirtualFromBase {
// cxx11-error@-1 {{not an integral constant expression}}
// cxx11-note@-2 {{call to virtual function}}
// cxx20_2b-error@-3 {{static assertion failed}}
// cxx20_2b-note@-4 {{8 == 16}}
// Non-virtual f(), OK.
constexpr X<X<S2>> xxs2;
constexpr X<S2> *q = const_cast<X<X<S2>>*>(&xxs2);
static_assert(q->f() == sizeof(S2), ""); // cxx20_2b-error {{static assertion failed}}
static_assert(q->f() == sizeof(S2), ""); // cxx20_2b-error {{static assertion failed}} \
// cxx20_2b-note {{16 == 8}}
}
namespace ConstexprConstructorRecovery {

View File

@ -149,7 +149,8 @@ enum Circular { // expected-note {{not complete until the closing '}
Circular_A = Circular(1), // expected-error {{'Circular' is an incomplete type}}
};
// Enumerators can be evaluated (they evaluate as zero, but we don't care).
static_assert(Circular_A == 0 && Circular_A != 0, ""); // expected-error {{static assertion failed}}
static_assert(Circular_A == 0 && Circular_A != 0, ""); // expected-error {{static assertion failed}} \
// expected-note {{evaluates to '0 != 0'}}
}
namespace test14 {

View File

@ -88,7 +88,8 @@ void foo6() {
static_assert(typename T::T(0));
// expected-error@-1{{static assertion failed due to requirement 'int(0)'}}
static_assert(sizeof(X<typename T::T>) == 0);
// expected-error@-1{{static assertion failed due to requirement 'sizeof(X<int>) == 0'}}
// expected-error@-1{{static assertion failed due to requirement 'sizeof(X<int>) == 0'}} \
// expected-note@-1 {{evaluates to '8 == 0'}}
static_assert((const X<typename T::T> *)nullptr);
// expected-error@-1{{static assertion failed due to requirement '(const X<int> *)nullptr'}}
static_assert(static_cast<const X<typename T::T> *>(nullptr));
@ -96,7 +97,8 @@ void foo6() {
static_assert((const X<typename T::T>[]){} == nullptr);
// expected-error@-1{{static assertion failed due to requirement '(const X<int>[0]){} == nullptr'}}
static_assert(sizeof(X<decltype(X<typename T::T>().X<typename T::T>::~X())>) == 0);
// expected-error@-1{{static assertion failed due to requirement 'sizeof(X<void>) == 0'}}
// expected-error@-1{{static assertion failed due to requirement 'sizeof(X<void>) == 0'}} \
// expected-note@-1 {{evaluates to '8 == 0'}}
static_assert(constexpr_return_false<typename T::T, typename T::U>());
// expected-error@-1{{static assertion failed due to requirement 'constexpr_return_false<int, float>()'}}
}

View File

@ -22,7 +22,8 @@ T<1> t1; // expected-note {{in instantiation of template class 'T<1>' requested
T<2> t2;
template<typename T> struct S {
static_assert(sizeof(T) > sizeof(char), "Type not big enough!"); // expected-error {{static assertion failed due to requirement 'sizeof(char) > sizeof(char)': Type not big enough!}}
static_assert(sizeof(T) > sizeof(char), "Type not big enough!"); // expected-error {{static assertion failed due to requirement 'sizeof(char) > sizeof(char)': Type not big enough!}} \
// expected-note {{1 > 1}}
};
S<char> s1; // expected-note {{in instantiation of template class 'S<char>' requested here}}
@ -215,3 +216,51 @@ static_assert(notBool, "message"); // expected-error {{value of type 's
static_assert(constexprNotBool, "message"); // expected-error {{value of type 'const NotBool' is not contextually convertible to 'bool'}}
static_assert(1 , "") // expected-error {{expected ';' after 'static_assert'}}
namespace Diagnostics {
/// No notes for literals.
static_assert(false, ""); // expected-error {{failed}}
static_assert(1.0 > 2.0, ""); // expected-error {{failed}}
static_assert('c' == 'd', ""); // expected-error {{failed}}
static_assert(1 == 2, ""); // expected-error {{failed}}
/// Simple things are ignored.
static_assert(1 == (-(1)), ""); //expected-error {{failed}}
/// Chars are printed as chars.
constexpr char getChar() {
return 'c';
}
static_assert(getChar() == 'a', ""); // expected-error {{failed}} \
// expected-note {{evaluates to ''c' == 'a''}}
/// Bools are printed as bools.
constexpr bool invert(bool b) {
return !b;
}
static_assert(invert(true) == invert(false), ""); // expected-error {{failed}} \
// expected-note {{evaluates to 'false == true'}}
/// No notes here since we compare a bool expression with a bool literal.
static_assert(invert(true) == true, ""); // expected-error {{failed}}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wc99-extensions"
constexpr _Complex float com = {5,6};
constexpr _Complex float com2 = {1, 9};
static_assert(com == com2, ""); // expected-error {{failed}} \
// expected-note {{evaluates to '(5 + 6i) == (1 + 9i)'}}
#pragma clang diagnostic pop
#define CHECK_4(x) ((x) == 4)
#define A_IS_B (a == b)
static_assert(CHECK_4(5), ""); // expected-error {{failed}}
constexpr int a = 4;
constexpr int b = 5;
static_assert(CHECK_4(a) && A_IS_B, ""); // expected-error {{failed}} \
// expected-note {{evaluates to '4 == 5'}}
}

View File

@ -31,7 +31,8 @@ namespace InstantiationDependent {
static_assert(b<char> == 1, ""); // expected-note {{in instantiation of}} expected-error {{not an integral constant}}
template<typename T> void f() {
static_assert(a<sizeof(sizeof(f(T())))> == 0, ""); // expected-error {{static assertion failed due to requirement 'a<sizeof (sizeof (f(type-parameter-0-0())))> == 0'}}
static_assert(a<sizeof(sizeof(f(T())))> == 0, ""); // expected-error {{static assertion failed due to requirement 'a<sizeof (sizeof (f(type-parameter-0-0())))> == 0'}} \
// expected-note {{evaluates to '1 == 0'}}
}
}

View File

@ -68,8 +68,10 @@ namespace PR46791 { // also PR45782
struct D : B, C {};
static_assert(trait<A>::specialization == 0);
static_assert(trait<B>::specialization == 1); // FIXME expected-error {{failed}}
static_assert(trait<C>::specialization == 2); // FIXME expected-error {{failed}}
static_assert(trait<B>::specialization == 1); // FIXME expected-error {{failed}} \
// expected-note {{evaluates to '0 == 1'}}
static_assert(trait<C>::specialization == 2); // FIXME expected-error {{failed}} \
// expected-note {{evaluates to '0 == 2'}}
static_assert(trait<D>::specialization == 0); // FIXME-error {{ambiguous partial specialization}}
}

View File

@ -184,7 +184,8 @@ namespace TemplateSpecializations {
namespace Diags {
struct A { int n, m; };
template<A a> struct X { static_assert(a.n == a.m); }; // expected-error {{static assertion failed due to requirement 'Diags::A{1, 2}.n == Diags::A{1, 2}.m'}}
template<A a> struct X { static_assert(a.n == a.m); }; // expected-error {{static assertion failed due to requirement 'Diags::A{1, 2}.n == Diags::A{1, 2}.m'}} \
// expected-note {{evaluates to '1 == 2'}}
template struct X<A{1, 2}>; // expected-note {{in instantiation of template class 'Diags::X<{1, 2}>' requested here}}
}