Skip to content

Commit

Permalink
Ignore FullExpr when traversing cast sub-expressions
Browse files Browse the repository at this point in the history
Full-expressions are Sema-generated implicit nodes that cover
constant-expressions and expressions-with-cleanup for temporaries.

Ignore those as part of implicit-ignore, and also remove too-aggressive
IgnoreImplicit (which includes nested ImplicitCastExprs, for example)
on unpacked sub-expressions.

Add some unittests to demonstrate that RecursiveASTVisitor sees through
ConstantExpr nodes correctly.

Adjust cxx2a-consteval test to cover diagnostics for nested consteval
expressions that were previously missed.

Fixes bug #53044.
  • Loading branch information
AaronBallman committed Mar 21, 2022
1 parent 276d214 commit 403d7d8
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 6 deletions.
9 changes: 5 additions & 4 deletions clang/lib/AST/Expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1871,6 +1871,9 @@ Expr *ignoreImplicitSemaNodes(Expr *E) {
if (auto *Binder = dyn_cast<CXXBindTemporaryExpr>(E))
return Binder->getSubExpr();

if (auto *Full = dyn_cast<FullExpr>(E))
return Full->getSubExpr();

return E;
}
} // namespace
Expand All @@ -1884,11 +1887,9 @@ Expr *CastExpr::getSubExprAsWritten() {
// Conversions by constructor and conversion functions have a
// subexpression describing the call; strip it off.
if (E->getCastKind() == CK_ConstructorConversion) {
SubExpr = IgnoreExprNodes(
cast<CXXConstructExpr>(SubExpr->IgnoreImplicit())->getArg(0),
ignoreImplicitSemaNodes);
SubExpr = IgnoreExprNodes(cast<CXXConstructExpr>(SubExpr)->getArg(0),
ignoreImplicitSemaNodes);
} else if (E->getCastKind() == CK_UserDefinedConversion) {
SubExpr = SubExpr->IgnoreImplicit();
assert((isa<CXXMemberCallExpr>(SubExpr) || isa<BlockExpr>(SubExpr)) &&
"Unexpected SubExpr for CK_UserDefinedConversion.");
if (auto *MCE = dyn_cast<CXXMemberCallExpr>(SubExpr))
Expand Down
16 changes: 14 additions & 2 deletions clang/test/SemaCXX/cxx2a-consteval.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -359,22 +359,34 @@ void test() {
// expected-note@-1 {{is not a constant expression}}
{ A k = to_lvalue_ref(A()); } // expected-error {{is not a constant expression}}
// expected-note@-1 {{is not a constant expression}} expected-note@-1 {{temporary created here}}
{ A k = to_lvalue_ref(A().ret_a()); } // expected-error {{is not a constant expression}}
// expected-note@-1 {{is not a constant expression}} expected-note@-1 {{temporary created here}}
{ A k = to_lvalue_ref(A().ret_a()); }
// expected-error@-1 {{'alloc::A::ret_a' is not a constant expression}}
// expected-note@-2 {{heap-allocated object is not a constant expression}}
// expected-error@-3 {{'alloc::to_lvalue_ref' is not a constant expression}}
// expected-note@-4 {{reference to temporary is not a constant expression}}
// expected-note@-5 {{temporary created here}}
{ int k = A().ret_a().ret_i(); }
// expected-error@-1 {{'alloc::A::ret_a' is not a constant expression}}
// expected-note@-2 {{heap-allocated object is not a constant expression}}
{ int k = by_value_a(A()); }
{ int k = const_a_ref(A()); }
{ int k = const_a_ref(a); }
{ int k = rvalue_ref(A()); }
{ int k = rvalue_ref(std::move(a)); }
{ int k = const_a_ref(A().ret_a()); }
// expected-error@-1 {{'alloc::A::ret_a' is not a constant expression}}
// expected-note@-2 {{is not a constant expression}}
{ int k = const_a_ref(to_lvalue_ref(A().ret_a())); }
// expected-error@-1 {{'alloc::A::ret_a' is not a constant expression}}
// expected-note@-2 {{is not a constant expression}}
{ int k = const_a_ref(to_lvalue_ref(std::move(a))); }
{ int k = by_value_a(A().ret_a()); }
{ int k = by_value_a(to_lvalue_ref(static_cast<const A&&>(a))); }
{ int k = (A().ret_a(), A().ret_i()); }// expected-error {{is not a constant expression}}
// expected-note@-1 {{is not a constant expression}}
{ int k = (const_a_ref(A().ret_a()), A().ret_i()); }
// expected-error@-1 {{'alloc::A::ret_a' is not a constant expression}}
// expected-note@-2 {{is not a constant expression}}
}

}
Expand Down
60 changes: 60 additions & 0 deletions clang/unittests/Tooling/CastExprTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,19 @@ namespace {

struct CastExprVisitor : TestVisitor<CastExprVisitor> {
std::function<void(ExplicitCastExpr *)> OnExplicitCast;
std::function<void(CastExpr *)> OnCast;

bool VisitExplicitCastExpr(ExplicitCastExpr *Expr) {
if (OnExplicitCast)
OnExplicitCast(Expr);
return true;
}

bool VisitCastExpr(CastExpr *Expr) {
if (OnCast)
OnCast(Expr);
return true;
}
};

TEST(CastExprTest, GetSubExprAsWrittenThroughMaterializedTemporary) {
Expand Down Expand Up @@ -54,4 +61,57 @@ TEST(CastExprTest, GetSubExprAsWrittenThroughConstantExpr) {
CastExprVisitor::Lang_CXX2a);
}

// Verify that getConversionFunction looks through a ConstantExpr for implicit
// constructor conversions (https://github.com/llvm/llvm-project/issues/53044):
//
// `-ImplicitCastExpr 'X' <ConstructorConversion>
// `-ConstantExpr 'X'
// |-value: Struct
// `-CXXConstructExpr 'X' 'void (const char *)'
// `-ImplicitCastExpr 'const char *' <ArrayToPointerDecay>
// `-StringLiteral 'const char [7]' lvalue "foobar"
TEST(CastExprTest, GetCtorConversionFunctionThroughConstantExpr) {
CastExprVisitor Visitor;
Visitor.OnCast = [](CastExpr *Expr) {
if (Expr->getCastKind() == CK_ConstructorConversion) {
auto *Conv = Expr->getConversionFunction();
EXPECT_TRUE(isa<CXXConstructorDecl>(Conv))
<< "Expected CXXConstructorDecl, but saw " << Conv->getDeclKindName();
}
};
Visitor.runOver("struct X { consteval X(const char *) {} };\n"
"void f() { X x = \"foobar\"; }\n",
CastExprVisitor::Lang_CXX2a);
}

// Verify that getConversionFunction looks through a ConstantExpr for implicit
// user-defined conversions.
//
// `-ImplicitCastExpr 'const char *' <UserDefinedConversion>
// `-ConstantExpr 'const char *'
// |-value: LValue
// `-CXXMemberCallExpr 'const char *'
// `-MemberExpr '<bound member function type>' .operator const char *
// `-DeclRefExpr 'const X' lvalue Var 'x' 'const X'
TEST(CastExprTest, GetUserDefinedConversionFunctionThroughConstantExpr) {
CastExprVisitor Visitor;
Visitor.OnCast = [](CastExpr *Expr) {
if (Expr->getCastKind() == CK_UserDefinedConversion) {
auto *Conv = Expr->getConversionFunction();
EXPECT_TRUE(isa<CXXMethodDecl>(Conv))
<< "Expected CXXMethodDecl, but saw " << Conv->getDeclKindName();
}
};
Visitor.runOver("struct X {\n"
" consteval operator const char *() const {\n"
" return nullptr;\n"
" }\n"
"};\n"
"const char *f() {\n"
" constexpr X x;\n"
" return x;\n"
"}\n",
CastExprVisitor::Lang_CXX2a);
}

} // namespace

0 comments on commit 403d7d8

Please sign in to comment.