From c689f6622f89dba2c01ea03462318006dac4785b Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 30 Nov 2016 00:13:55 +0000 Subject: [PATCH] [c++1z] Improve support for -fno-exceptions: we can't just ignore exception specifications in this mode in C++17, since they're part of the function type, so check and diagnose them like we would if exceptions were enabled. Better ideas welcome. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@288220 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/clang/Basic/DiagnosticSemaKinds.td | 2 + include/clang/Sema/Sema.h | 6 +- lib/Sema/SemaDecl.cpp | 9 -- lib/Sema/SemaExceptionSpec.cpp | 87 ++++++++++++------- test/SemaCXX/builtin-exception-spec.cpp | 1 + test/SemaCXX/cxx1z-noexcept-function-type.cpp | 7 +- 6 files changed, 62 insertions(+), 50 deletions(-) diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td index 884310046e..b223b511e3 100644 --- a/include/clang/Basic/DiagnosticSemaKinds.td +++ b/include/clang/Basic/DiagnosticSemaKinds.td @@ -1306,6 +1306,8 @@ def err_mismatched_exception_spec : Error< "exception specification in declaration does not match previous declaration">; def ext_mismatched_exception_spec : ExtWarn, InGroup; +def warn_mismatched_exception_spec_no_exceptions : ExtWarn, + InGroup>; def err_override_exception_spec : Error< "exception specification of overriding function is more lax than " "base version">; diff --git a/include/clang/Sema/Sema.h b/include/clang/Sema/Sema.h index 05031edbfe..0bd588936b 100644 --- a/include/clang/Sema/Sema.h +++ b/include/clang/Sema/Sema.h @@ -1330,11 +1330,7 @@ public: bool CheckEquivalentExceptionSpec( const PartialDiagnostic &DiagID, const PartialDiagnostic & NoteID, const FunctionProtoType *Old, SourceLocation OldLoc, - const FunctionProtoType *New, SourceLocation NewLoc, - bool *MissingExceptionSpecification = nullptr, - bool *MissingEmptyExceptionSpecification = nullptr, - bool AllowNoexceptAllMatchWithNoSpec = false, - bool IsOperatorNew = false); + const FunctionProtoType *New, SourceLocation NewLoc); bool CheckExceptionSpecSubset(const PartialDiagnostic &DiagID, const PartialDiagnostic &NestedDiagID, const PartialDiagnostic &NoteID, diff --git a/lib/Sema/SemaDecl.cpp b/lib/Sema/SemaDecl.cpp index e2951cf174..6114c610bb 100644 --- a/lib/Sema/SemaDecl.cpp +++ b/lib/Sema/SemaDecl.cpp @@ -2949,15 +2949,6 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, // but do not necessarily update the type of New. if (CheckEquivalentExceptionSpec(Old, New)) return true; - // If exceptions are disabled, we might not have resolved the exception spec - // of one or both declarations. Do so now in C++1z, so that we can properly - // compare the types. - if (getLangOpts().CPlusPlus1z) { - for (QualType T : {Old->getType(), New->getType()}) - if (auto *FPT = T->getAs()) - if (isUnresolvedExceptionSpec(FPT->getExceptionSpecType())) - ResolveExceptionSpec(New->getLocation(), FPT); - } OldQType = Context.getCanonicalType(Old->getType()); NewQType = Context.getCanonicalType(New->getType()); diff --git a/lib/Sema/SemaExceptionSpec.cpp b/lib/Sema/SemaExceptionSpec.cpp index ae728422ab..e95482679b 100644 --- a/lib/Sema/SemaExceptionSpec.cpp +++ b/lib/Sema/SemaExceptionSpec.cpp @@ -207,6 +207,14 @@ Sema::UpdateExceptionSpec(FunctionDecl *FD, Context.adjustExceptionSpec(cast(Redecl), ESI); } +static bool CheckEquivalentExceptionSpecImpl( + Sema &S, const PartialDiagnostic &DiagID, const PartialDiagnostic &NoteID, + const FunctionProtoType *Old, SourceLocation OldLoc, + const FunctionProtoType *New, SourceLocation NewLoc, + bool *MissingExceptionSpecification = nullptr, + bool *MissingEmptyExceptionSpecification = nullptr, + bool AllowNoexceptAllMatchWithNoSpec = false, bool IsOperatorNew = false); + /// Determine whether a function has an implicitly-generated exception /// specification. static bool hasImplicitExceptionSpec(FunctionDecl *Decl) { @@ -229,6 +237,12 @@ static bool hasImplicitExceptionSpec(FunctionDecl *Decl) { } bool Sema::CheckEquivalentExceptionSpec(FunctionDecl *Old, FunctionDecl *New) { + // Just completely ignore this under -fno-exceptions prior to C++1z. + // In C++1z onwards, the exception specification is part of the type and + // we will diagnose mismatches anyway, so it's better to check for them here. + if (!getLangOpts().CXXExceptions && !getLangOpts().CPlusPlus1z) + return false; + OverloadedOperatorKind OO = New->getDeclName().getCXXOverloadedOperator(); bool IsOperatorNew = OO == OO_New || OO == OO_Array_New; bool MissingExceptionSpecification = false; @@ -243,8 +257,8 @@ bool Sema::CheckEquivalentExceptionSpec(FunctionDecl *Old, FunctionDecl *New) { // Check the types as written: they must match before any exception // specification adjustment is applied. - if (!CheckEquivalentExceptionSpec( - PDiag(DiagID), PDiag(diag::note_previous_declaration), + if (!CheckEquivalentExceptionSpecImpl( + *this, PDiag(DiagID), PDiag(diag::note_previous_declaration), Old->getType()->getAs(), Old->getLocation(), New->getType()->getAs(), New->getLocation(), &MissingExceptionSpecification, &MissingEmptyExceptionSpecification, @@ -395,11 +409,15 @@ bool Sema::CheckEquivalentExceptionSpec(FunctionDecl *Old, FunctionDecl *New) { bool Sema::CheckEquivalentExceptionSpec( const FunctionProtoType *Old, SourceLocation OldLoc, const FunctionProtoType *New, SourceLocation NewLoc) { + if (!getLangOpts().CXXExceptions) + return false; + unsigned DiagID = diag::err_mismatched_exception_spec; if (getLangOpts().MicrosoftExt) DiagID = diag::ext_mismatched_exception_spec; - bool Result = CheckEquivalentExceptionSpec(PDiag(DiagID), - PDiag(diag::note_previous_declaration), Old, OldLoc, New, NewLoc); + bool Result = CheckEquivalentExceptionSpecImpl( + *this, PDiag(DiagID), PDiag(diag::note_previous_declaration), + Old, OldLoc, New, NewLoc); // In Microsoft mode, mismatching exception specifications just cause a warning. if (getLangOpts().MicrosoftExt) @@ -413,30 +431,23 @@ bool Sema::CheckEquivalentExceptionSpec( /// \return \c false if the exception specifications match, \c true if there is /// a problem. If \c true is returned, either a diagnostic has already been /// produced or \c *MissingExceptionSpecification is set to \c true. -bool Sema::CheckEquivalentExceptionSpec(const PartialDiagnostic &DiagID, - const PartialDiagnostic &NoteID, - const FunctionProtoType *Old, - SourceLocation OldLoc, - const FunctionProtoType *New, - SourceLocation NewLoc, - bool *MissingExceptionSpecification, - bool*MissingEmptyExceptionSpecification, - bool AllowNoexceptAllMatchWithNoSpec, - bool IsOperatorNew) { - // Just completely ignore this under -fno-exceptions. - if (!getLangOpts().CXXExceptions) - return false; - +static bool CheckEquivalentExceptionSpecImpl( + Sema &S, const PartialDiagnostic &DiagID, const PartialDiagnostic &NoteID, + const FunctionProtoType *Old, SourceLocation OldLoc, + const FunctionProtoType *New, SourceLocation NewLoc, + bool *MissingExceptionSpecification, + bool *MissingEmptyExceptionSpecification, + bool AllowNoexceptAllMatchWithNoSpec, bool IsOperatorNew) { if (MissingExceptionSpecification) *MissingExceptionSpecification = false; if (MissingEmptyExceptionSpecification) *MissingEmptyExceptionSpecification = false; - Old = ResolveExceptionSpec(NewLoc, Old); + Old = S.ResolveExceptionSpec(NewLoc, Old); if (!Old) return false; - New = ResolveExceptionSpec(NewLoc, New); + New = S.ResolveExceptionSpec(NewLoc, New); if (!New) return false; @@ -470,8 +481,8 @@ bool Sema::CheckEquivalentExceptionSpec(const PartialDiagnostic &DiagID, if (OldEST == EST_None && NewEST == EST_None) return false; - FunctionProtoType::NoexceptResult OldNR = Old->getNoexceptSpec(Context); - FunctionProtoType::NoexceptResult NewNR = New->getNoexceptSpec(Context); + FunctionProtoType::NoexceptResult OldNR = Old->getNoexceptSpec(S.Context); + FunctionProtoType::NoexceptResult NewNR = New->getNoexceptSpec(S.Context); if (OldNR == FunctionProtoType::NR_BadNoexcept || NewNR == FunctionProtoType::NR_BadNoexcept) return false; @@ -486,9 +497,9 @@ bool Sema::CheckEquivalentExceptionSpec(const PartialDiagnostic &DiagID, if (OldNR != NewNR && OldNR != FunctionProtoType::NR_NoNoexcept && NewNR != FunctionProtoType::NR_NoNoexcept) { - Diag(NewLoc, DiagID); + S.Diag(NewLoc, DiagID); if (NoteID.getDiagID() != 0 && OldLoc.isValid()) - Diag(OldLoc, NoteID); + S.Diag(OldLoc, NoteID); return true; } @@ -526,7 +537,7 @@ bool Sema::CheckEquivalentExceptionSpec(const PartialDiagnostic &DiagID, // As a special compatibility feature, under C++0x we accept no spec and // throw(std::bad_alloc) as equivalent for operator new and operator new[]. // This is because the implicit declaration changed, but old code would break. - if (getLangOpts().CPlusPlus11 && IsOperatorNew) { + if (S.getLangOpts().CPlusPlus11 && IsOperatorNew) { const FunctionProtoType *WithExceptions = nullptr; if (OldEST == EST_None && NewEST == EST_Dynamic) WithExceptions = New; @@ -567,9 +578,9 @@ bool Sema::CheckEquivalentExceptionSpec(const PartialDiagnostic &DiagID, return true; } - Diag(NewLoc, DiagID); + S.Diag(NewLoc, DiagID); if (NoteID.getDiagID() != 0 && OldLoc.isValid()) - Diag(OldLoc, NoteID); + S.Diag(OldLoc, NoteID); return true; } @@ -581,11 +592,11 @@ bool Sema::CheckEquivalentExceptionSpec(const PartialDiagnostic &DiagID, // to the second. llvm::SmallPtrSet OldTypes, NewTypes; for (const auto &I : Old->exceptions()) - OldTypes.insert(Context.getCanonicalType(I).getUnqualifiedType()); + OldTypes.insert(S.Context.getCanonicalType(I).getUnqualifiedType()); for (const auto &I : New->exceptions()) { - CanQualType TypePtr = Context.getCanonicalType(I).getUnqualifiedType(); - if(OldTypes.count(TypePtr)) + CanQualType TypePtr = S.Context.getCanonicalType(I).getUnqualifiedType(); + if (OldTypes.count(TypePtr)) NewTypes.insert(TypePtr); else Success = false; @@ -596,12 +607,24 @@ bool Sema::CheckEquivalentExceptionSpec(const PartialDiagnostic &DiagID, if (Success) { return false; } - Diag(NewLoc, DiagID); + S.Diag(NewLoc, DiagID); if (NoteID.getDiagID() != 0 && OldLoc.isValid()) - Diag(OldLoc, NoteID); + S.Diag(OldLoc, NoteID); return true; } +bool Sema::CheckEquivalentExceptionSpec(const PartialDiagnostic &DiagID, + const PartialDiagnostic &NoteID, + const FunctionProtoType *Old, + SourceLocation OldLoc, + const FunctionProtoType *New, + SourceLocation NewLoc) { + if (!getLangOpts().CXXExceptions) + return false; + return CheckEquivalentExceptionSpecImpl(*this, DiagID, NoteID, Old, OldLoc, + New, NewLoc); +} + /// CheckExceptionSpecSubset - Check whether the second function type's /// exception specification is a subset (or equivalent) of the first function /// type. This is used by override and pointer assignment checks. diff --git a/test/SemaCXX/builtin-exception-spec.cpp b/test/SemaCXX/builtin-exception-spec.cpp index 590cd3c35d..9845172bcd 100644 --- a/test/SemaCXX/builtin-exception-spec.cpp +++ b/test/SemaCXX/builtin-exception-spec.cpp @@ -1,4 +1,5 @@ // RUN: %clang_cc1 -isystem %S/Inputs -fsyntax-only -verify %s +// RUN: %clang_cc1 -isystem %S/Inputs -fsyntax-only -verify -std=c++1z %s // expected-no-diagnostics #include diff --git a/test/SemaCXX/cxx1z-noexcept-function-type.cpp b/test/SemaCXX/cxx1z-noexcept-function-type.cpp index d9233e681f..105aaa92f9 100644 --- a/test/SemaCXX/cxx1z-noexcept-function-type.cpp +++ b/test/SemaCXX/cxx1z-noexcept-function-type.cpp @@ -10,10 +10,9 @@ template void redecl1() noexcept(noexcept(T())) {} // expected-error template void redecl2() noexcept(A); // expected-note {{previous}} template void redecl2() noexcept(B); // expected-error {{conflicting types}} -// These have the same canonical type. -// FIXME: It's not clear whether this is supposed to be valid. -template void redecl3() throw(A); -template void redecl3() throw(B); +// These have the same canonical type, but are still different. +template void redecl3() throw(A); // expected-note {{previous}} +template void redecl3() throw(B); // expected-error {{does not match previous}} typedef int I; template void redecl4(I) noexcept(B); -- 2.40.0