Skip to content

[Clang][Sema] Fix exception specification comparison for functions with different template depths #111561

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
135 changes: 123 additions & 12 deletions clang/lib/Sema/SemaExceptionSpec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
//
//===----------------------------------------------------------------------===//

#include "clang/Sema/SemaInternal.h"
#include "clang/AST/ASTMutationListener.h"
#include "clang/AST/CXXInheritance.h"
#include "clang/AST/Expr.h"
Expand All @@ -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>
Expand Down Expand Up @@ -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(Old->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(
Expand Down Expand Up @@ -501,6 +519,110 @@ 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() == 0)
return ExceptionSpec;

Sema::SFINAETrap SFINAE(S, /*AccessCheckingSFINAE=*/false);

auto *FD = const_cast<FunctionDecl *>(DeclInfo.getDecl()->getAsFunction());
Sema::InstantiatingTemplate Inst(
S, DeclInfo.getLocation(), FD,
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);

for (auto *PVD : FD->parameters()) {
if (!PVD->isParameterPack()) {
ScopeForParameters.InstantiatedLocal(PVD, PVD);
continue;
}
// This is hacky: we're mapping the parameter pack to a size-of-1 argument
// to avoid building SubstTemplateTypeParmPackTypes for
// PackExpansionTypes. The SubstTemplateTypeParmPackType node would
// otherwise reference the AssociatedDecl of the template arguments, which
// is, in this case, the template declaration.
//
// However, as we are in the process of comparing potential
// re-declarations, the canonical declaration is the declaration itself at
// this point. So if we didn't expand these packs, we would end up with an
// incorrect profile difference because we will be profiling the
// canonical types!
//
// FIXME: Improve the "no-transform" machinery in FindInstantiatedDecl so
// that we can eliminate the Scope in the cases where the declarations are
// not necessarily instantiated. It would also benefit the noexcept
// specifier comparison.
ScopeForParameters.MakeInstantiatedLocalArgPack(PVD);
ScopeForParameters.InstantiatedLocalPackArg(PVD, PVD);
}

// 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.
Sema::ContextRAII ContextScope(S, FD);

auto *MD = dyn_cast<CXXMethodDecl>(FD);
Sema::CXXThisScopeRAII ThisScope(
S, MD ? MD->getParent() : nullptr,
MD ? MD->getMethodQualifiers() : Qualifiers{}, MD != nullptr);

EnterExpressionEvaluationContext ConstantEvaluated(
S, Sema::ExpressionEvaluationContext::ConstantEvaluated);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should that not be be Unevaluated?
https://eel.is/c++draft/expr.unary.noexcept#1


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.
///
Expand Down Expand Up @@ -574,17 +696,6 @@ static bool CheckEquivalentExceptionSpecImpl(
}
}

// 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;
}

// Dynamic exception specifications with the same set of adjusted types
// are compatible.
if (OldEST == EST_Dynamic && NewEST == EST_Dynamic) {
Expand Down
37 changes: 37 additions & 0 deletions clang/test/CXX/basic/basic.link/p11.cpp
Original file line number Diff line number Diff line change
@@ -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}}
};
}
Loading