[clang] [Clang][Sema] Fix exception specification comparison for functions with different template depths (PR #111561)
Krystian Stasiowski via cfe-commits
cfe-commits at lists.llvm.org
Tue Oct 8 11:14:11 PDT 2024
https://github.com/sdkrystian updated https://github.com/llvm/llvm-project/pull/111561
>From da01bdac8a99a127a74073df4a505bdc936291e2 Mon Sep 17 00:00:00 2001
From: Krystian Stasiowski <sdkrystian at gmail.com>
Date: Tue, 8 Oct 2024 12:54:26 -0400
Subject: [PATCH 1/2] [Clang][Sema] Fix exception specification comparison for
functions with different template depths
---
clang/include/clang/Sema/Sema.h | 5 ++
clang/lib/Sema/SemaExceptionSpec.cpp | 105 +++++++++++++++++++++++-
clang/test/CXX/basic/basic.link/p11.cpp | 37 +++++++++
3 files changed, 146 insertions(+), 1 deletion(-)
create mode 100644 clang/test/CXX/basic/basic.link/p11.cpp
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 7ff9c2754a6fe0..eea950582929d9 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -5026,6 +5026,11 @@ class Sema final : public SemaBase {
/// special member function.
void EvaluateImplicitExceptionSpec(SourceLocation Loc, FunctionDecl *FD);
+ bool AreExceptionSpecsEqual(const NamedDecl *Old,
+ const Expr *OldExceptionSpec,
+ const NamedDecl *New,
+ const Expr *NewExceptionSpec);
+
/// Check the given exception-specification and update the
/// exception specification information with the results.
void checkExceptionSpecification(bool IsTopLevel,
diff --git a/clang/lib/Sema/SemaExceptionSpec.cpp b/clang/lib/Sema/SemaExceptionSpec.cpp
index dbddd6c370aa07..c74686073df228 100644
--- a/clang/lib/Sema/SemaExceptionSpec.cpp
+++ b/clang/lib/Sema/SemaExceptionSpec.cpp
@@ -10,7 +10,6 @@
//
//===----------------------------------------------------------------------===//
-#include "clang/Sema/SemaInternal.h"
#include "clang/AST/ASTMutationListener.h"
#include "clang/AST/CXXInheritance.h"
#include "clang/AST/Expr.h"
@@ -19,6 +18,9 @@
#include "clang/AST/TypeLoc.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/SourceManager.h"
+#include "clang/Sema/EnterExpressionEvaluationContext.h"
+#include "clang/Sema/SemaInternal.h"
+#include "clang/Sema/Template.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallString.h"
#include <optional>
@@ -314,6 +316,22 @@ bool Sema::CheckEquivalentExceptionSpec(FunctionDecl *Old, FunctionDecl *New) {
return false;
}
+ if (Old->getExceptionSpecType() == EST_DependentNoexcept &&
+ New->getExceptionSpecType() == EST_DependentNoexcept) {
+ const auto *OldType = Old->getType()->getAs<FunctionProtoType>();
+ const auto *NewType = New->getType()->getAs<FunctionProtoType>();
+ OldType = ResolveExceptionSpec(New->getLocation(), OldType);
+ if (!OldType)
+ return false;
+ NewType = ResolveExceptionSpec(New->getLocation(), NewType);
+ if (!NewType)
+ return false;
+
+ if (AreExceptionSpecsEqual(Old, OldType->getNoexceptExpr(), New,
+ NewType->getNoexceptExpr()))
+ return false;
+ }
+
// Check the types as written: they must match before any exception
// specification adjustment is applied.
if (!CheckEquivalentExceptionSpecImpl(
@@ -501,6 +519,89 @@ bool Sema::CheckEquivalentExceptionSpec(
return Result;
}
+static const Expr *SubstituteExceptionSpecWithoutEvaluation(
+ Sema &S, const Sema::TemplateCompareNewDeclInfo &DeclInfo,
+ const Expr *ExceptionSpec) {
+ MultiLevelTemplateArgumentList MLTAL = S.getTemplateInstantiationArgs(
+ DeclInfo.getDecl(), DeclInfo.getLexicalDeclContext(),
+ /*Final=*/false, /*Innermost=*/std::nullopt,
+ /*RelativeToPrimary=*/true, /*ForConstraintInstantiation=*/true);
+
+ if (!MLTAL.getNumSubstitutedLevels())
+ return ExceptionSpec;
+
+ Sema::SFINAETrap SFINAE(S, /*AccessCheckingSFINAE=*/false);
+
+ Sema::InstantiatingTemplate Inst(
+ S, DeclInfo.getLocation(),
+ const_cast<FunctionDecl *>(DeclInfo.getDecl()->getAsFunction()),
+ Sema::InstantiatingTemplate::ExceptionSpecification());
+ if (Inst.isInvalid())
+ return nullptr;
+
+ // Set up a dummy 'instantiation' scope in the case of reference to function
+ // parameters that the surrounding function hasn't been instantiated yet. Note
+ // this may happen while we're comparing two templates' constraint
+ // equivalence.
+ LocalInstantiationScope ScopeForParameters(S);
+ if (auto *FD = DeclInfo.getDecl()->getAsFunction())
+ for (auto *PVD : FD->parameters())
+ ScopeForParameters.InstantiatedLocal(PVD, PVD);
+
+ std::optional<Sema::CXXThisScopeRAII> ThisScope;
+
+ // See TreeTransform::RebuildTemplateSpecializationType. A context scope is
+ // essential for having an injected class as the canonical type for a template
+ // specialization type at the rebuilding stage. This guarantees that, for
+ // out-of-line definitions, injected class name types and their equivalent
+ // template specializations can be profiled to the same value, which makes it
+ // possible that e.g. constraints involving C<Class<T>> and C<Class> are
+ // perceived identical.
+ std::optional<Sema::ContextRAII> ContextScope;
+ if (auto *RD = dyn_cast<CXXRecordDecl>(DeclInfo.getDeclContext())) {
+ ThisScope.emplace(S, const_cast<CXXRecordDecl *>(RD), Qualifiers());
+ ContextScope.emplace(S, const_cast<DeclContext *>(cast<DeclContext>(RD)),
+ /*NewThisContext=*/false);
+ }
+
+ EnterExpressionEvaluationContext ConstantEvaluated(
+ S, Sema::ExpressionEvaluationContext::ConstantEvaluated);
+
+ ExprResult SubstExceptionSpec =
+ S.SubstExpr(const_cast<clang::Expr *>(ExceptionSpec), MLTAL);
+ if (SFINAE.hasErrorOccurred() || !SubstExceptionSpec.isUsable())
+ return nullptr;
+ return SubstExceptionSpec.get();
+}
+
+bool Sema::AreExceptionSpecsEqual(const NamedDecl *Old,
+ const Expr *OldExceptionSpec,
+ const NamedDecl *New,
+ const Expr *NewExceptionSpec) {
+ if (OldExceptionSpec == NewExceptionSpec)
+ return true;
+ if (Old && New &&
+ Old->getLexicalDeclContext() != New->getLexicalDeclContext()) {
+ if (const Expr *SubstExceptionSpec =
+ SubstituteExceptionSpecWithoutEvaluation(*this, Old,
+ OldExceptionSpec))
+ OldExceptionSpec = SubstExceptionSpec;
+ else
+ return false;
+ if (const Expr *SubstExceptionSpec =
+ SubstituteExceptionSpecWithoutEvaluation(*this, New,
+ NewExceptionSpec))
+ NewExceptionSpec = SubstExceptionSpec;
+ else
+ return false;
+ }
+
+ llvm::FoldingSetNodeID ID1, ID2;
+ OldExceptionSpec->Profile(ID1, Context, /*Canonical=*/true);
+ NewExceptionSpec->Profile(ID2, Context, /*Canonical=*/true);
+ return ID1 == ID2;
+}
+
/// CheckEquivalentExceptionSpec - Check if the two types have compatible
/// exception specifications. See C++ [except.spec]p3.
///
@@ -574,6 +675,7 @@ static bool CheckEquivalentExceptionSpecImpl(
}
}
+#if 0
// C++14 [except.spec]p3:
// Two exception-specifications are compatible if [...] both have the form
// noexcept(constant-expression) and the constant-expressions are equivalent
@@ -584,6 +686,7 @@ static bool CheckEquivalentExceptionSpecImpl(
if (OldFSN == NewFSN)
return false;
}
+#endif
// Dynamic exception specifications with the same set of adjusted types
// are compatible.
diff --git a/clang/test/CXX/basic/basic.link/p11.cpp b/clang/test/CXX/basic/basic.link/p11.cpp
new file mode 100644
index 00000000000000..e244336417fd67
--- /dev/null
+++ b/clang/test/CXX/basic/basic.link/p11.cpp
@@ -0,0 +1,37 @@
+// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify %s
+
+namespace MemberSpecialization {
+ template<typename T>
+ struct A {
+ template<bool B>
+ void f() noexcept(B);
+
+ template<bool B>
+ void g() noexcept(B); // expected-note {{previous declaration is here}}
+ };
+
+ template<>
+ template<bool B>
+ void A<int>::f() noexcept(B);
+
+ template<>
+ template<bool B>
+ void A<int>::g() noexcept(!B); // expected-error {{exception specification in declaration does not match previous declaration}}
+}
+
+namespace Friend {
+ template<bool B>
+ void f() noexcept(B);
+
+ template<bool B>
+ void g() noexcept(B); // expected-note {{previous declaration is here}}
+
+ template<typename T>
+ struct A {
+ template<bool B>
+ friend void f() noexcept(B);
+
+ template<bool B>
+ friend void g() noexcept(!B); // expected-error {{exception specification in declaration does not match previous declaration}}
+ };
+}
>From 68b5bda6c9bbf72614e0c3f379fc7e1caeeb6faa Mon Sep 17 00:00:00 2001
From: Krystian Stasiowski <sdkrystian at gmail.com>
Date: Tue, 8 Oct 2024 14:13:59 -0400
Subject: [PATCH 2/2] [FOLD] remove dead code
---
clang/lib/Sema/SemaExceptionSpec.cpp | 13 -------------
1 file changed, 13 deletions(-)
diff --git a/clang/lib/Sema/SemaExceptionSpec.cpp b/clang/lib/Sema/SemaExceptionSpec.cpp
index c74686073df228..9ef096ca13e6b4 100644
--- a/clang/lib/Sema/SemaExceptionSpec.cpp
+++ b/clang/lib/Sema/SemaExceptionSpec.cpp
@@ -675,19 +675,6 @@ static bool CheckEquivalentExceptionSpecImpl(
}
}
-#if 0
- // C++14 [except.spec]p3:
- // Two exception-specifications are compatible if [...] both have the form
- // noexcept(constant-expression) and the constant-expressions are equivalent
- if (OldEST == EST_DependentNoexcept && NewEST == EST_DependentNoexcept) {
- llvm::FoldingSetNodeID OldFSN, NewFSN;
- Old->getNoexceptExpr()->Profile(OldFSN, S.Context, true);
- New->getNoexceptExpr()->Profile(NewFSN, S.Context, true);
- if (OldFSN == NewFSN)
- return false;
- }
-#endif
-
// Dynamic exception specifications with the same set of adjusted types
// are compatible.
if (OldEST == EST_Dynamic && NewEST == EST_Dynamic) {
More information about the cfe-commits
mailing list