[clang] [clang] Fix a crash issue that caused by handling of fields with initializers in nested anonymous unions (PR #113049)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Nov 25 04:54:25 PST 2024
https://github.com/yronglin updated https://github.com/llvm/llvm-project/pull/113049
>From 804b1032cb23cea8fa705a0d2130b1f95185c949 Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Wed, 20 Nov 2024 23:45:59 +0800
Subject: [PATCH 1/4] [clang] Fix a crash issue that caused by handling of
fields with initializers in nested anonymous unions
Signed-off-by: yronglin <yronglin777 at gmail.com>
---
clang/include/clang/AST/Decl.h | 10 +---------
clang/include/clang/AST/DeclCXX.h | 15 +++++++++++++++
clang/lib/AST/Decl.cpp | 16 ++++++++++++++++
clang/lib/AST/DeclCXX.cpp | 8 ++++++++
clang/lib/Sema/SemaDeclCXX.cpp | 1 +
.../SemaCXX/cxx1y-initializer-aggregates.cpp | 11 +++++++++++
6 files changed, 52 insertions(+), 9 deletions(-)
diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index 7ff35d73df5997..95d28a8e35cab7 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -3219,15 +3219,7 @@ class FieldDecl : public DeclaratorDecl, public Mergeable<FieldDecl> {
public:
/// Remove the C++11 in-class initializer from this member.
- void removeInClassInitializer() {
- assert(hasInClassInitializer() && "no initializer to remove");
- StorageKind = ISK_NoInit;
- if (BitField) {
- // Read the bit width before we change the active union member.
- Expr *ExistingBitWidth = InitAndBitWidth->BitWidth;
- BitWidth = ExistingBitWidth;
- }
- }
+ void removeInClassInitializer();
/// Determine whether this member captures the variable length array
/// type.
diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index 2693cc0e95b4b2..7a0644652f950b 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -269,6 +269,7 @@ class CXXRecordDecl : public RecordDecl {
friend void FunctionDecl::setIsPureVirtual(bool);
friend void TagDecl::startDefinition();
+ friend void FieldDecl::removeInClassInitializer();
/// Values used in DefinitionData fields to represent special members.
enum SpecialMemberFlags {
@@ -319,6 +320,9 @@ class CXXRecordDecl : public RecordDecl {
/// The number of virtual base class specifiers in VBases.
unsigned NumVBases = 0;
+ /// The number of C++11 in-class-initializers in this class.
+ unsigned NumInClassInitializers = 0;
+
/// Base classes of this class.
///
/// FIXME: This is wasted space for a union.
@@ -497,6 +501,17 @@ class CXXRecordDecl : public RecordDecl {
/// whenever a member is added to this record.
void addedMember(Decl *D);
+ /// Decreasing the number of C++11 in-class-initializers, and update the
+ /// HasInClassInitializer if there is no in-class-initializer in this class.
+ ///
+ /// This routine helps maintain the number of C++11 in-class-initializers.
+ /// The RecordDecl::hasInClassInitializer() needs to be consistent with the
+ /// FieldDecl::hasInClassInitializer(), When calling
+ /// FieldDecl::hasInClassInitializer() to remove the in-class-initializer in
+ /// the field, we need to check whether there are any in-class-initializers in
+ /// this class, and update HasInClassInitializer to the correct value.
+ void removeInClassInitializer();
+
void markedVirtualFunctionPure();
/// Get the head of our list of friend declarations, possibly
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index f083ffff87a8ec..01398d3800816a 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -4584,6 +4584,22 @@ void FieldDecl::setInClassInitializer(Expr *NewInit) {
setLazyInClassInitializer(LazyDeclStmtPtr(NewInit));
}
+void FieldDecl::removeInClassInitializer() {
+ assert(hasInClassInitializer() && "no initializer to remove");
+ StorageKind = ISK_NoInit;
+ if (BitField) {
+ // Read the bit width before we change the active union member.
+ Expr *ExistingBitWidth = InitAndBitWidth->BitWidth;
+ BitWidth = ExistingBitWidth;
+ }
+
+ // The RecordDecl::hasInClassInitializer() needs to be consistent with the
+ // FieldDecl::hasInClassInitializer(). Check the number of C++11
+ // in-class-initializers in the parent class.
+ if (CXXRecordDecl *RD = dyn_cast<CXXRecordDecl>(getParent()))
+ RD->removeInClassInitializer();
+}
+
void FieldDecl::setLazyInClassInitializer(LazyDeclStmtPtr NewInit) {
assert(hasInClassInitializer() && !getInClassInitializer());
if (BitField)
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index 08615d4393f5d1..83a7a055742e7a 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -1145,6 +1145,7 @@ void CXXRecordDecl::addedMember(Decl *D) {
(Field->isAnonymousStructOrUnion() &&
Field->getType()->getAsCXXRecordDecl()->hasInClassInitializer())) {
data().HasInClassInitializer = true;
+ data().NumInClassInitializers++;
// C++11 [class]p5:
// A default constructor is trivial if [...] no non-static data member
@@ -1441,6 +1442,13 @@ void CXXRecordDecl::addedMember(Decl *D) {
}
}
+void CXXRecordDecl::removeInClassInitializer() {
+ assert(data().NumInClassInitializers > 0 &&
+ "No member initializer in this class");
+ if (--data().NumInClassInitializers == 0)
+ data().HasInClassInitializer = false;
+}
+
bool CXXRecordDecl::isLiteral() const {
const LangOptions &LangOpts = getLangOpts();
if (!(LangOpts.CPlusPlus20 ? hasConstexprDestructor()
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 38f808a470aa87..c62c3381ada525 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -4088,6 +4088,7 @@ void Sema::ActOnFinishCXXInClassMemberInitializer(Decl *D,
assert((isa<MSPropertyDecl>(D) || FD->getInClassInitStyle() != ICIS_NoInit) &&
"must set init style when field is created");
+ /// FIXME: We might create an RecoveryExpr for the in-class-initializer.
if (!InitExpr) {
D->setInvalidDecl();
if (FD)
diff --git a/clang/test/SemaCXX/cxx1y-initializer-aggregates.cpp b/clang/test/SemaCXX/cxx1y-initializer-aggregates.cpp
index 03a6800898d18f..8360b8fd7d8ee2 100644
--- a/clang/test/SemaCXX/cxx1y-initializer-aggregates.cpp
+++ b/clang/test/SemaCXX/cxx1y-initializer-aggregates.cpp
@@ -115,3 +115,14 @@ namespace nested_union {
// of Test3, or we should exclude f(Test3) as a candidate.
static_assert(f({1}) == 2, ""); // expected-error {{call to 'f' is ambiguous}}
}
+
+// Fix crash issue https://github.com/llvm/llvm-project/issues/112560.
+// Make sure clang compiles the following code without crashing:
+namespace GH112560 {
+union U {
+ int f = ; // expected-error {{expected expression}}
+};
+void foo() {
+ U g{};
+}
+} // namespace GH112560
>From de4a9d55e723d4662b7ad6264d33df524d56833c Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Sat, 23 Nov 2024 23:26:06 +0800
Subject: [PATCH 2/4] Revert "[clang] Fix a crash issue that caused by handling
of fields with initializers in nested anonymous unions"
This reverts commit 804b1032cb23cea8fa705a0d2130b1f95185c949.
---
clang/include/clang/AST/Decl.h | 10 +++++++++-
clang/include/clang/AST/DeclCXX.h | 15 ---------------
clang/lib/AST/Decl.cpp | 16 ----------------
clang/lib/AST/DeclCXX.cpp | 8 --------
clang/lib/Sema/SemaDeclCXX.cpp | 1 -
.../SemaCXX/cxx1y-initializer-aggregates.cpp | 11 -----------
6 files changed, 9 insertions(+), 52 deletions(-)
diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index 95d28a8e35cab7..7ff35d73df5997 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -3219,7 +3219,15 @@ class FieldDecl : public DeclaratorDecl, public Mergeable<FieldDecl> {
public:
/// Remove the C++11 in-class initializer from this member.
- void removeInClassInitializer();
+ void removeInClassInitializer() {
+ assert(hasInClassInitializer() && "no initializer to remove");
+ StorageKind = ISK_NoInit;
+ if (BitField) {
+ // Read the bit width before we change the active union member.
+ Expr *ExistingBitWidth = InitAndBitWidth->BitWidth;
+ BitWidth = ExistingBitWidth;
+ }
+ }
/// Determine whether this member captures the variable length array
/// type.
diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index 7a0644652f950b..2693cc0e95b4b2 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -269,7 +269,6 @@ class CXXRecordDecl : public RecordDecl {
friend void FunctionDecl::setIsPureVirtual(bool);
friend void TagDecl::startDefinition();
- friend void FieldDecl::removeInClassInitializer();
/// Values used in DefinitionData fields to represent special members.
enum SpecialMemberFlags {
@@ -320,9 +319,6 @@ class CXXRecordDecl : public RecordDecl {
/// The number of virtual base class specifiers in VBases.
unsigned NumVBases = 0;
- /// The number of C++11 in-class-initializers in this class.
- unsigned NumInClassInitializers = 0;
-
/// Base classes of this class.
///
/// FIXME: This is wasted space for a union.
@@ -501,17 +497,6 @@ class CXXRecordDecl : public RecordDecl {
/// whenever a member is added to this record.
void addedMember(Decl *D);
- /// Decreasing the number of C++11 in-class-initializers, and update the
- /// HasInClassInitializer if there is no in-class-initializer in this class.
- ///
- /// This routine helps maintain the number of C++11 in-class-initializers.
- /// The RecordDecl::hasInClassInitializer() needs to be consistent with the
- /// FieldDecl::hasInClassInitializer(), When calling
- /// FieldDecl::hasInClassInitializer() to remove the in-class-initializer in
- /// the field, we need to check whether there are any in-class-initializers in
- /// this class, and update HasInClassInitializer to the correct value.
- void removeInClassInitializer();
-
void markedVirtualFunctionPure();
/// Get the head of our list of friend declarations, possibly
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 01398d3800816a..f083ffff87a8ec 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -4584,22 +4584,6 @@ void FieldDecl::setInClassInitializer(Expr *NewInit) {
setLazyInClassInitializer(LazyDeclStmtPtr(NewInit));
}
-void FieldDecl::removeInClassInitializer() {
- assert(hasInClassInitializer() && "no initializer to remove");
- StorageKind = ISK_NoInit;
- if (BitField) {
- // Read the bit width before we change the active union member.
- Expr *ExistingBitWidth = InitAndBitWidth->BitWidth;
- BitWidth = ExistingBitWidth;
- }
-
- // The RecordDecl::hasInClassInitializer() needs to be consistent with the
- // FieldDecl::hasInClassInitializer(). Check the number of C++11
- // in-class-initializers in the parent class.
- if (CXXRecordDecl *RD = dyn_cast<CXXRecordDecl>(getParent()))
- RD->removeInClassInitializer();
-}
-
void FieldDecl::setLazyInClassInitializer(LazyDeclStmtPtr NewInit) {
assert(hasInClassInitializer() && !getInClassInitializer());
if (BitField)
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index 83a7a055742e7a..08615d4393f5d1 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -1145,7 +1145,6 @@ void CXXRecordDecl::addedMember(Decl *D) {
(Field->isAnonymousStructOrUnion() &&
Field->getType()->getAsCXXRecordDecl()->hasInClassInitializer())) {
data().HasInClassInitializer = true;
- data().NumInClassInitializers++;
// C++11 [class]p5:
// A default constructor is trivial if [...] no non-static data member
@@ -1442,13 +1441,6 @@ void CXXRecordDecl::addedMember(Decl *D) {
}
}
-void CXXRecordDecl::removeInClassInitializer() {
- assert(data().NumInClassInitializers > 0 &&
- "No member initializer in this class");
- if (--data().NumInClassInitializers == 0)
- data().HasInClassInitializer = false;
-}
-
bool CXXRecordDecl::isLiteral() const {
const LangOptions &LangOpts = getLangOpts();
if (!(LangOpts.CPlusPlus20 ? hasConstexprDestructor()
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index c62c3381ada525..38f808a470aa87 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -4088,7 +4088,6 @@ void Sema::ActOnFinishCXXInClassMemberInitializer(Decl *D,
assert((isa<MSPropertyDecl>(D) || FD->getInClassInitStyle() != ICIS_NoInit) &&
"must set init style when field is created");
- /// FIXME: We might create an RecoveryExpr for the in-class-initializer.
if (!InitExpr) {
D->setInvalidDecl();
if (FD)
diff --git a/clang/test/SemaCXX/cxx1y-initializer-aggregates.cpp b/clang/test/SemaCXX/cxx1y-initializer-aggregates.cpp
index 8360b8fd7d8ee2..03a6800898d18f 100644
--- a/clang/test/SemaCXX/cxx1y-initializer-aggregates.cpp
+++ b/clang/test/SemaCXX/cxx1y-initializer-aggregates.cpp
@@ -115,14 +115,3 @@ namespace nested_union {
// of Test3, or we should exclude f(Test3) as a candidate.
static_assert(f({1}) == 2, ""); // expected-error {{call to 'f' is ambiguous}}
}
-
-// Fix crash issue https://github.com/llvm/llvm-project/issues/112560.
-// Make sure clang compiles the following code without crashing:
-namespace GH112560 {
-union U {
- int f = ; // expected-error {{expected expression}}
-};
-void foo() {
- U g{};
-}
-} // namespace GH112560
>From 882fd4e32395348514db2cde741bd9713f07f88b Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Mon, 25 Nov 2024 00:10:15 +0800
Subject: [PATCH 3/4] Recovery from the invalid in-class-initializer
Signed-off-by: yronglin <yronglin777 at gmail.com>
---
clang/include/clang/Sema/Sema.h | 2 +-
clang/lib/Parse/ParseCXXInlineMethods.cpp | 3 +--
clang/lib/Sema/SemaDeclCXX.cpp | 24 +++++++++++--------
clang/lib/Sema/SemaInit.cpp | 12 ++++++++++
.../SemaCXX/cxx1y-initializer-aggregates.cpp | 11 +++++++++
5 files changed, 39 insertions(+), 13 deletions(-)
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 0faa5aed4eec3b..b0dd4494b7c3b2 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -5287,7 +5287,7 @@ class Sema final : public SemaBase {
/// is complete.
void ActOnFinishCXXInClassMemberInitializer(Decl *VarDecl,
SourceLocation EqualLoc,
- Expr *Init);
+ ExprResult Init);
/// Handle a C++ member initializer using parentheses syntax.
MemInitResult
diff --git a/clang/lib/Parse/ParseCXXInlineMethods.cpp b/clang/lib/Parse/ParseCXXInlineMethods.cpp
index b461743833c82a..79e61af5f06850 100644
--- a/clang/lib/Parse/ParseCXXInlineMethods.cpp
+++ b/clang/lib/Parse/ParseCXXInlineMethods.cpp
@@ -722,8 +722,7 @@ void Parser::ParseLexedMemberInitializer(LateParsedMemberInitializer &MI) {
ExprResult Init = ParseCXXMemberInitializer(MI.Field, /*IsFunction=*/false,
EqualLoc);
- Actions.ActOnFinishCXXInClassMemberInitializer(MI.Field, EqualLoc,
- Init.get());
+ Actions.ActOnFinishCXXInClassMemberInitializer(MI.Field, EqualLoc, Init);
// The next token should be our artificial terminating EOF token.
if (Tok.isNot(tok::eof)) {
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 38f808a470aa87..19acac7bf6d09e 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -4080,24 +4080,28 @@ ExprResult Sema::ConvertMemberDefaultInitExpression(FieldDecl *FD,
void Sema::ActOnFinishCXXInClassMemberInitializer(Decl *D,
SourceLocation InitLoc,
- Expr *InitExpr) {
+ ExprResult InitExpr) {
// Pop the notional constructor scope we created earlier.
PopFunctionScopeInfo(nullptr, D);
- FieldDecl *FD = dyn_cast<FieldDecl>(D);
- assert((isa<MSPropertyDecl>(D) || FD->getInClassInitStyle() != ICIS_NoInit) &&
- "must set init style when field is created");
-
- if (!InitExpr) {
+ // Microsoft C++'s property declaration cannot have a default member
+ // initializer.
+ if (isa<MSPropertyDecl>(D)) {
D->setInvalidDecl();
- if (FD)
- FD->removeInClassInitializer();
return;
}
- if (DiagnoseUnexpandedParameterPack(InitExpr, UPPC_Initializer)) {
+ FieldDecl *FD = dyn_cast<FieldDecl>(D);
+ assert((FD && FD->getInClassInitStyle() != ICIS_NoInit) &&
+ "must set init style when field is created");
+
+ if (!InitExpr.isUsable() ||
+ DiagnoseUnexpandedParameterPack(InitExpr.get(), UPPC_Initializer)) {
FD->setInvalidDecl();
- FD->removeInClassInitializer();
+ ExprResult RecoveryInit =
+ CreateRecoveryExpr(InitLoc, InitLoc, {}, FD->getType());
+ if (RecoveryInit.isUsable())
+ FD->setInClassInitializer(RecoveryInit.get());
return;
}
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index f560865681fa5a..5f86a14a8026af 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -750,6 +750,18 @@ void InitListChecker::FillInEmptyInitForField(unsigned Init, FieldDecl *Field,
if (Field->hasInClassInitializer()) {
if (VerifyOnly)
return;
+
+ // We do not want to aggressively set the hadError flag and cutoff
+ // parsing. Try to recover when in-class-initializer is a RecoveryExpr.
+ if (isa_and_nonnull<RecoveryExpr>(Field->getInClassInitializer())) {
+ if (Init < NumInits)
+ ILE->setInit(Init, Field->getInClassInitializer());
+ else
+ ILE->updateInit(SemaRef.Context, Init,
+ Field->getInClassInitializer());
+ return;
+ }
+
ExprResult DIE;
{
// Enter a default initializer rebuild context, then we can support
diff --git a/clang/test/SemaCXX/cxx1y-initializer-aggregates.cpp b/clang/test/SemaCXX/cxx1y-initializer-aggregates.cpp
index 03a6800898d18f..8360b8fd7d8ee2 100644
--- a/clang/test/SemaCXX/cxx1y-initializer-aggregates.cpp
+++ b/clang/test/SemaCXX/cxx1y-initializer-aggregates.cpp
@@ -115,3 +115,14 @@ namespace nested_union {
// of Test3, or we should exclude f(Test3) as a candidate.
static_assert(f({1}) == 2, ""); // expected-error {{call to 'f' is ambiguous}}
}
+
+// Fix crash issue https://github.com/llvm/llvm-project/issues/112560.
+// Make sure clang compiles the following code without crashing:
+namespace GH112560 {
+union U {
+ int f = ; // expected-error {{expected expression}}
+};
+void foo() {
+ U g{};
+}
+} // namespace GH112560
>From cf5a038e7d3296e4d5f3faa4a1d8108cedcb0e26 Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Mon, 25 Nov 2024 20:53:57 +0800
Subject: [PATCH 4/4] Add test for ast-dump
Signed-off-by: yronglin <yronglin777 at gmail.com>
---
clang/test/AST/ast-dump-recovery.cpp | 33 ++++++++++++++++++++++++++++
1 file changed, 33 insertions(+)
diff --git a/clang/test/AST/ast-dump-recovery.cpp b/clang/test/AST/ast-dump-recovery.cpp
index a88dff471d9f04..2ebf6574689d34 100644
--- a/clang/test/AST/ast-dump-recovery.cpp
+++ b/clang/test/AST/ast-dump-recovery.cpp
@@ -460,3 +460,36 @@ void RecoveryForStmtCond() {
// CHECK-NEXT: `-CompoundStmt {{.*}}
for (int i = 0; i < invalid; ++i) {}
}
+
+// Fix crash issue https://github.com/llvm/llvm-project/issues/112560.
+// Make sure clang compiles the following code without crashing:
+
+// CHECK:NamespaceDecl {{.*}} GH112560
+// CHECK-NEXT: |-CXXRecordDecl {{.*}} referenced union U definition
+// CHECK-NEXT: | |-DefinitionData {{.*}}
+// CHECK-NEXT: | | |-DefaultConstructor {{.*}}
+// CHECK-NEXT: | | |-CopyConstructor {{.*}}
+// CHECK-NEXT: | | |-MoveConstructor {{.*}}
+// CHECK-NEXT: | | |-CopyAssignment {{.*}}
+// CHECK-NEXT: | | |-MoveAssignment {{.*}}
+// CHECK-NEXT: | | `-Destructor {{.*}}
+// CHECK-NEXT: | |-CXXRecordDecl {{.*}} implicit union U
+// CHECK-NEXT: | `-FieldDecl {{.*}} invalid f 'int'
+// CHECK-NEXT: | `-RecoveryExpr {{.*}} 'int' contains-errors
+// DISABLED-NOT: -RecoveryExpr {{.*}} contains-errors
+namespace GH112560 {
+union U {
+ int f = ;
+};
+
+// CHECK: FunctionDecl {{.*}} foo 'void ()'
+// CHECK-NEXT: `-CompoundStmt {{.*}}
+// CHECK-NEXT: `-DeclStmt {{.*}}
+// CHECK-NEXT: `-VarDecl {{.*}} g 'U':'GH112560::U' listinit
+// CHECK-NEXT: `-InitListExpr {{.*}} 'U':'GH112560::U' contains-errors field Field {{.*}} 'f' 'int'
+// CHECK-NEXT: `-RecoveryExpr {{.*}} 'int' contains-errors
+// DISABLED-NOT: -RecoveryExpr {{.*}} contains-errors
+void foo() {
+ U g{};
+}
+} // namespace GH112560
More information about the cfe-commits
mailing list