Skip to content

Commit 5c4f2cf

Browse files
committed
[Clang][Sema] Fix exception specification comparison for functions with different template depths
1 parent 3dedcab commit 5c4f2cf

File tree

3 files changed

+146
-1
lines changed

3 files changed

+146
-1
lines changed

clang/include/clang/Sema/Sema.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5028,6 +5028,11 @@ class Sema final : public SemaBase {
50285028
/// special member function.
50295029
void EvaluateImplicitExceptionSpec(SourceLocation Loc, FunctionDecl *FD);
50305030

5031+
bool AreExceptionSpecsEqual(const NamedDecl *Old,
5032+
const Expr *OldExceptionSpec,
5033+
const NamedDecl *New,
5034+
const Expr *NewExceptionSpec);
5035+
50315036
/// Check the given exception-specification and update the
50325037
/// exception specification information with the results.
50335038
void checkExceptionSpecification(bool IsTopLevel,

clang/lib/Sema/SemaExceptionSpec.cpp

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
#include "clang/Sema/SemaInternal.h"
1413
#include "clang/AST/ASTMutationListener.h"
1514
#include "clang/AST/CXXInheritance.h"
1615
#include "clang/AST/Expr.h"
@@ -19,6 +18,9 @@
1918
#include "clang/AST/TypeLoc.h"
2019
#include "clang/Basic/Diagnostic.h"
2120
#include "clang/Basic/SourceManager.h"
21+
#include "clang/Sema/EnterExpressionEvaluationContext.h"
22+
#include "clang/Sema/SemaInternal.h"
23+
#include "clang/Sema/Template.h"
2224
#include "llvm/ADT/SmallPtrSet.h"
2325
#include "llvm/ADT/SmallString.h"
2426
#include <optional>
@@ -314,6 +316,22 @@ bool Sema::CheckEquivalentExceptionSpec(FunctionDecl *Old, FunctionDecl *New) {
314316
return false;
315317
}
316318

319+
if (Old->getExceptionSpecType() == EST_DependentNoexcept &&
320+
New->getExceptionSpecType() == EST_DependentNoexcept) {
321+
const auto *OldType = Old->getType()->getAs<FunctionProtoType>();
322+
const auto *NewType = New->getType()->getAs<FunctionProtoType>();
323+
OldType = ResolveExceptionSpec(New->getLocation(), OldType);
324+
if (!OldType)
325+
return false;
326+
NewType = ResolveExceptionSpec(New->getLocation(), NewType);
327+
if (!NewType)
328+
return false;
329+
330+
if (AreExceptionSpecsEqual(Old, OldType->getNoexceptExpr(), New,
331+
NewType->getNoexceptExpr()))
332+
return false;
333+
}
334+
317335
// Check the types as written: they must match before any exception
318336
// specification adjustment is applied.
319337
if (!CheckEquivalentExceptionSpecImpl(
@@ -501,6 +519,89 @@ bool Sema::CheckEquivalentExceptionSpec(
501519
return Result;
502520
}
503521

522+
static const Expr *SubstituteExceptionSpecWithoutEvaluation(
523+
Sema &S, const Sema::TemplateCompareNewDeclInfo &DeclInfo,
524+
const Expr *ExceptionSpec) {
525+
MultiLevelTemplateArgumentList MLTAL = S.getTemplateInstantiationArgs(
526+
DeclInfo.getDecl(), DeclInfo.getLexicalDeclContext(),
527+
/*Final=*/false, /*Innermost=*/std::nullopt,
528+
/*RelativeToPrimary=*/true, /*ForConstraintInstantiation=*/true);
529+
530+
if (!MLTAL.getNumSubstitutedLevels())
531+
return ExceptionSpec;
532+
533+
Sema::SFINAETrap SFINAE(S, /*AccessCheckingSFINAE=*/false);
534+
535+
Sema::InstantiatingTemplate Inst(
536+
S, DeclInfo.getLocation(),
537+
const_cast<FunctionDecl *>(DeclInfo.getDecl()->getAsFunction()),
538+
Sema::InstantiatingTemplate::ExceptionSpecification());
539+
if (Inst.isInvalid())
540+
return nullptr;
541+
542+
// Set up a dummy 'instantiation' scope in the case of reference to function
543+
// parameters that the surrounding function hasn't been instantiated yet. Note
544+
// this may happen while we're comparing two templates' constraint
545+
// equivalence.
546+
LocalInstantiationScope ScopeForParameters(S);
547+
if (auto *FD = DeclInfo.getDecl()->getAsFunction())
548+
for (auto *PVD : FD->parameters())
549+
ScopeForParameters.InstantiatedLocal(PVD, PVD);
550+
551+
std::optional<Sema::CXXThisScopeRAII> ThisScope;
552+
553+
// See TreeTransform::RebuildTemplateSpecializationType. A context scope is
554+
// essential for having an injected class as the canonical type for a template
555+
// specialization type at the rebuilding stage. This guarantees that, for
556+
// out-of-line definitions, injected class name types and their equivalent
557+
// template specializations can be profiled to the same value, which makes it
558+
// possible that e.g. constraints involving C<Class<T>> and C<Class> are
559+
// perceived identical.
560+
std::optional<Sema::ContextRAII> ContextScope;
561+
if (auto *RD = dyn_cast<CXXRecordDecl>(DeclInfo.getDeclContext())) {
562+
ThisScope.emplace(S, const_cast<CXXRecordDecl *>(RD), Qualifiers());
563+
ContextScope.emplace(S, const_cast<DeclContext *>(cast<DeclContext>(RD)),
564+
/*NewThisContext=*/false);
565+
}
566+
567+
EnterExpressionEvaluationContext ConstantEvaluated(
568+
S, Sema::ExpressionEvaluationContext::ConstantEvaluated);
569+
570+
ExprResult SubstExceptionSpec =
571+
S.SubstExpr(const_cast<clang::Expr *>(ExceptionSpec), MLTAL);
572+
if (SFINAE.hasErrorOccurred() || !SubstExceptionSpec.isUsable())
573+
return nullptr;
574+
return SubstExceptionSpec.get();
575+
}
576+
577+
bool Sema::AreExceptionSpecsEqual(const NamedDecl *Old,
578+
const Expr *OldExceptionSpec,
579+
const NamedDecl *New,
580+
const Expr *NewExceptionSpec) {
581+
if (OldExceptionSpec == NewExceptionSpec)
582+
return true;
583+
if (Old && New &&
584+
Old->getLexicalDeclContext() != New->getLexicalDeclContext()) {
585+
if (const Expr *SubstExceptionSpec =
586+
SubstituteExceptionSpecWithoutEvaluation(*this, Old,
587+
OldExceptionSpec))
588+
OldExceptionSpec = SubstExceptionSpec;
589+
else
590+
return false;
591+
if (const Expr *SubstExceptionSpec =
592+
SubstituteExceptionSpecWithoutEvaluation(*this, New,
593+
NewExceptionSpec))
594+
NewExceptionSpec = SubstExceptionSpec;
595+
else
596+
return false;
597+
}
598+
599+
llvm::FoldingSetNodeID ID1, ID2;
600+
OldExceptionSpec->Profile(ID1, Context, /*Canonical=*/true);
601+
NewExceptionSpec->Profile(ID2, Context, /*Canonical=*/true);
602+
return ID1 == ID2;
603+
}
604+
504605
/// CheckEquivalentExceptionSpec - Check if the two types have compatible
505606
/// exception specifications. See C++ [except.spec]p3.
506607
///
@@ -574,6 +675,7 @@ static bool CheckEquivalentExceptionSpecImpl(
574675
}
575676
}
576677

678+
#if 0
577679
// C++14 [except.spec]p3:
578680
// Two exception-specifications are compatible if [...] both have the form
579681
// noexcept(constant-expression) and the constant-expressions are equivalent
@@ -584,6 +686,7 @@ static bool CheckEquivalentExceptionSpecImpl(
584686
if (OldFSN == NewFSN)
585687
return false;
586688
}
689+
#endif
587690

588691
// Dynamic exception specifications with the same set of adjusted types
589692
// are compatible.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify %s
2+
3+
namespace MemberSpecialization {
4+
template<typename T>
5+
struct A {
6+
template<bool B>
7+
void f() noexcept(B);
8+
9+
template<bool B>
10+
void g() noexcept(B); // expected-note {{previous declaration is here}}
11+
};
12+
13+
template<>
14+
template<bool B>
15+
void A<int>::f() noexcept(B);
16+
17+
template<>
18+
template<bool B>
19+
void A<int>::g() noexcept(!B); // expected-error {{exception specification in declaration does not match previous declaration}}
20+
}
21+
22+
namespace Friend {
23+
template<bool B>
24+
void f() noexcept(B);
25+
26+
template<bool B>
27+
void g() noexcept(B); // expected-note {{previous declaration is here}}
28+
29+
template<typename T>
30+
struct A {
31+
template<bool B>
32+
friend void f() noexcept(B);
33+
34+
template<bool B>
35+
friend void g() noexcept(!B); // expected-error {{exception specification in declaration does not match previous declaration}}
36+
};
37+
}

0 commit comments

Comments
 (0)