Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -6341,6 +6341,15 @@ "cannot use '%0' with exceptions disabled">; def err_objc_exceptions_disabled : Error< "cannot use '%0' with Objective-C exceptions disabled">; +def warn_throw_in_noexcept_func + : Warning<"%0 has a non-throwing exception specification but can still " + "throw, resulting in unexpected program termination">, + InGroup; +def note_throw_in_dtor + : Note<"destructor or deallocator has a (possibly implicit) non-throwing " + "excepton specification">; +def note_throw_in_function + : Note<"non-throwing function declare here">; def err_seh_try_outside_functions : Error< "cannot use SEH '__try' in blocks, captured regions, or Obj-C method decls">; def err_mixing_cxx_try_seh_try : Error< Index: lib/Sema/AnalysisBasedWarnings.cpp =================================================================== --- lib/Sema/AnalysisBasedWarnings.cpp +++ lib/Sema/AnalysisBasedWarnings.cpp @@ -279,6 +279,152 @@ } //===----------------------------------------------------------------------===// +// Check for throw in a non-throwing function. +//===----------------------------------------------------------------------===// +enum ThrowState { + FoundNoPathForThrow, + FoundPathForThrow, + FoundPathWithNoThrowOutFunction, +}; + +static bool isThrowCaught(const CXXThrowExpr *Throw, + const CXXCatchStmt *Catch) { + const Type *CaughtType = Catch->getCaughtType().getTypePtrOrNull(); + const Type *ThrowType = nullptr; + if (Throw->getSubExpr()) + ThrowType = Throw->getSubExpr()->getType().getTypePtrOrNull(); + + if (ThrowType == nullptr) + return false; + if (ThrowType && ThrowType->isReferenceType()) + ThrowType = ThrowType->castAs() + ->getPointeeType() + ->getUnqualifiedDesugaredType(); + if (CaughtType == nullptr) + return true; + if (CaughtType && CaughtType->isReferenceType()) + CaughtType = CaughtType->castAs() + ->getPointeeType() + ->getUnqualifiedDesugaredType(); + if (CaughtType == ThrowType) + return true; + const CXXRecordDecl *CaughtAsRecordType = + CaughtType->getPointeeCXXRecordDecl(); + const CXXRecordDecl *ThrowTypeAsRecordType = ThrowType->getAsCXXRecordDecl(); + if (CaughtAsRecordType && ThrowTypeAsRecordType) + return ThrowTypeAsRecordType->isDerivedFrom(CaughtAsRecordType); + return false; +} + +static bool isThrowCaughtByHandlers(const CXXThrowExpr *CE, + const CXXTryStmt *TryStmt) { + for (unsigned H = 0, E = TryStmt->getNumHandlers(); H < E; ++H) { + if (isThrowCaught(CE, TryStmt->getHandler(H))) + return true; + } + return false; +} + +static bool doesThrowEscapePath(CFGBlock Block, SourceLocation &OpLoc) { + for (const auto &B : Block) { + if (B.getKind() != CFGElement::Statement) + continue; + const CXXThrowExpr *CE = + dyn_cast(B.getAs()->getStmt()); + if (!CE) + continue; + + OpLoc = CE->getThrowLoc(); + for (const auto &I : Block.succs()) { + if (!I.isReachable()) + continue; + if (const CXXTryStmt *Terminator = + dyn_cast_or_null(I->getTerminator())) + if (isThrowCaughtByHandlers(CE, Terminator)) + return false; + } + return true; + } + return false; +} + +static bool hasThrowOutNonThrowingFunc(SourceLocation &OpLoc, CFG *BodyCFG) { + + unsigned ExitID = BodyCFG->getExit().getBlockID(); + + SmallVector States(BodyCFG->getNumBlockIDs(), + FoundNoPathForThrow); + States[BodyCFG->getEntry().getBlockID()] = FoundPathWithNoThrowOutFunction; + + SmallVector Stack; + Stack.push_back(&BodyCFG->getEntry()); + while (!Stack.empty()) { + CFGBlock *CurBlock = Stack.back(); + Stack.pop_back(); + + unsigned ID = CurBlock->getBlockID(); + ThrowState CurState = States[ID]; + if (CurState == FoundPathWithNoThrowOutFunction) { + if (ExitID == ID) + continue; + + if (doesThrowEscapePath(*CurBlock, OpLoc)) + CurState = FoundPathForThrow; + } + + // Loop over successor blocks and add them to the Stack if their state + // changes. + for (const auto &I : CurBlock->succs()) + if (I.isReachable()) { + unsigned NextID = I->getBlockID(); + if (NextID == ExitID && CurState == FoundPathForThrow) { + States[NextID] = CurState; + } else if (States[NextID] < CurState) { + States[NextID] = CurState; + Stack.push_back(I); + } + } + } + // Return true if the exit node is reachable, and only reachable through + // a throw expression. + return States[ExitID] == FoundPathForThrow; +} + +static void EmitDiagForCXXThrowInNonThrowingFunc(SourceLocation OpLoc, Sema &S, + const FunctionDecl *FD) { + if (!S.getSourceManager().isInSystemHeader(OpLoc)) { + S.Diag(OpLoc, diag::warn_throw_in_noexcept_func) << FD; + if (S.getLangOpts().CPlusPlus11 && + (isa(FD) || + FD->getDeclName().getCXXOverloadedOperator() == OO_Delete || + FD->getDeclName().getCXXOverloadedOperator() == OO_Array_Delete)) + S.Diag(FD->getLocation(), diag::note_throw_in_dtor); + else + S.Diag(FD->getLocation(), diag::note_throw_in_function); + } +} + +static void checkThrowInNonThrowingFunc(Sema &S, const FunctionDecl *FD, + AnalysisDeclContext &AC) { + CFG *BodyCFG = AC.getCFG(); + if (!BodyCFG) + return; + if (BodyCFG->getExit().pred_empty()) + return; + SourceLocation OpLoc; + if (hasThrowOutNonThrowingFunc(OpLoc, BodyCFG)) + EmitDiagForCXXThrowInNonThrowingFunc(OpLoc, S, FD); +} + +static bool isNoexcept(const FunctionDecl *FD) { + const FunctionProtoType *FPT = FD->getType()->castAs(); + if (FPT->getExceptionSpecType() != EST_None && + FPT->isNothrow(FD->getASTContext())) + return true; + return false; +} + +//===----------------------------------------------------------------------===// // Check for missing return value. //===----------------------------------------------------------------------===// @@ -2127,6 +2273,12 @@ } } + // Check for throw out of non-throwing function. + if (!Diags.isIgnored(diag::warn_throw_in_noexcept_func, D->getLocStart())) + if (const FunctionDecl *FD = dyn_cast(D)) + if (S.getLangOpts().CPlusPlus && isNoexcept(FD)) + checkThrowInNonThrowingFunc(S, FD, AC); + // If none of the previous checks caused a CFG build, trigger one here // for -Wtautological-overlap-compare if (!Diags.isIgnored(diag::warn_tautological_overlap_comparison, Index: test/CXX/except/except.spec/p11.cpp =================================================================== --- test/CXX/except/except.spec/p11.cpp +++ test/CXX/except/except.spec/p11.cpp @@ -1,12 +1,11 @@ // RUN: %clang_cc1 -std=c++11 -fexceptions -fcxx-exceptions -fsyntax-only -verify %s -// expected-no-diagnostics // This is the "let the user shoot themselves in the foot" clause. -void f() noexcept { - throw 0; // no-error +void f() noexcept { // expected-note {{non-throwing function declare here}} + throw 0; // expected-warning {{has a non-throwing exception specification but}} } -void g() throw() { - throw 0; // no-error +void g() throw() { // expected-note {{non-throwing function declare here}} + throw 0; // expected-warning {{has a non-throwing exception specification but}} } void h() throw(int) { throw 0.0; // no-error Index: test/SemaCXX/warn-throw-out-noexcept-func.cpp =================================================================== --- test/SemaCXX/warn-throw-out-noexcept-func.cpp +++ test/SemaCXX/warn-throw-out-noexcept-func.cpp @@ -0,0 +1,247 @@ +// RUN: %clang_cc1 %s -fdelayed-template-parsing -fcxx-exceptions -fexceptions -fsyntax-only -Wexceptions -verify -std=c++11 +struct A { + ~A(); +}; // implicitly noexcept(true) +A::~A() { // expected-note {{destructor or deallocator has a}} + throw 1; // expected-warning {{has a non-throwing exception specification but}} +} +struct B { + int i; + ~B() noexcept(true) {} +}; +struct R : A { + B b; + ~R() { // expected-note {{destructor or deallocator has a}} + throw 1; // expected-warning {{has a non-throwing exception specification but}} + } +}; + +struct M : A { + B b; + ~M() noexcept(false); +}; + +M::~M() noexcept(false) { + throw 1; +} + +struct N : A { + B b; + ~N(); //implicitly noexcept(true) +}; + +N::~N() { // expected-note {{destructor or deallocator has a}} + throw 1; // expected-warning {{has a non-throwing exception specification but}} +} +struct X : A { + B b; + ~X() noexcept { // expected-note {{destructor or deallocator has a}} + throw 1; // expected-warning {{has a non-throwing exception specification but}} + } +}; +struct Y : A { + B b; + ~Y() noexcept(true) { // expected-note {{destructor or deallocator has a}} + throw 1; // expected-warning {{has a non-throwing exception specification but}} + } +}; +struct C { + int i; + ~C() noexcept(false) {} +}; +struct D : A { + C c; + ~D() { //implicitly noexcept(false) + throw 1; + } +}; +struct E : A { + C c; + ~E(); //implicitly noexcept(false) +}; +E::~E() //implicitly noexcept(false) +{ + throw 1; +} + +template +class A1 { + T b; + +public: + ~A1() { // expected-note {{destructor or deallocator has a}} + throw 1; // expected-warning {{has a non-throwing exception specification but}} + } +}; +template +struct B1 { + T i; + ~B1() noexcept(true) {} +}; +template +struct R1 : A1 //expected-note {{in instantiation of member function}} +{ + B1 b; + ~R1() { // expected-note {{destructor or deallocator has a}} + throw 1; // expected-warning {{has a non-throwing exception specification but}} + } +}; +template +struct S1 : A1 { + B1 b; + ~S1() noexcept { // expected-note {{destructor or deallocator has a}} + throw 1; // expected-warning {{has a non-throwing exception specification but}} + } +}; +void operator delete(void *ptr) noexcept { // expected-note {{destructor or deallocator has a}} + throw 1; // expected-warning {{has a non-throwing exception specification but}} +} +struct except_fun { + static const bool i = false; +}; +struct noexcept_fun { + static const bool i = true; +}; +template +struct dependent_warn { + ~dependent_warn() noexcept(T::i) { + throw 1; + } +}; +template +struct dependent_warn_noexcept { + ~dependent_warn_noexcept() noexcept(T::i) { // expected-note {{destructor or deallocator has a}} + throw 1; // expected-warning {{has a non-throwing exception specification but}} + } +}; +template +struct dependent_warn_both { + ~dependent_warn_both() noexcept(T::i) { // expected-note {{destructor or deallocator has a}} + throw 1; // expected-warning {{has a non-throwing exception specification but}} + } +}; +void foo() noexcept { //expected-note {{non-throwing function declare here}} + throw 1; // expected-warning {{has a non-throwing exception specification but}} +} +void bar() noexcept { + try { + throw 1; + } catch (...) { + } +} +void f() noexcept { + try { + throw 12; + } catch (int) { + } +} +void g() noexcept { + try { + throw 12; + } catch (...) { + } +} + +void h() noexcept { //expected-note {{non-throwing function declare here}} + try { + throw 12; // expected-warning {{has a non-throwing exception specification but}} + } catch (const char *) { + } +} + +void i() noexcept { //expected-note {{non-throwing function declare here}} + try { + throw 12; + } catch (int) { + throw; // expected-warning {{has a non-throwing exception specification but}} + } +} +void j() noexcept { //expected-note {{non-throwing function declare here}} + try { + throw 12; + } catch (int) { + throw "haha"; // expected-warning {{has a non-throwing exception specification but}} + } +} + +void k() noexcept { //expected-note {{non-throwing function declare here}} + try { + throw 12; + } catch (...) { + throw; // expected-warning {{has a non-throwing exception specification but}} + } +} + +void loo(int i) noexcept { //expected-note {{non-throwing function declare here}} + if (i) + try { + throw 12; + } catch (int) { + throw "haha"; //expected-warning {{has a non-throwing exception specification but}} + } + i = 10; +} + +void loo1() noexcept { + if (0) + throw 12; +} + +void loo2() noexcept { //expected-note {{non-throwing function declare here}} + if (1) + throw 12; // expected-warning {{has a non-throwing exception specification but}} +} +struct S {}; + +void l() noexcept { //expected-note {{non-throwing function declare here}} + try { + throw S{}; //expected-warning {{has a non-throwing exception specification but}} + } catch (S *s) { + } +} + +void m() noexcept { + try { + const S &s = S{}; + throw s; + } catch (S s) { + } + +} +void n() noexcept { + try { + S s = S{}; + throw s; + } catch (const S &s) { + } +} +void o() noexcept { //expected-note {{non-throwing function declare here}} + try { + throw; //expected-warning {{has a non-throwing exception specification but}} + } catch (...) { + } +} + +#define NOEXCEPT noexcept +void with_macro() NOEXCEPT { //expected-note {{non-throwing function declare here}} + throw 1; // expected-warning {{has a non-throwing exception specification but}} +} + +void with_try_block() try { + throw 2; +} catch (...) { +} + +void with_try_block1() noexcept try { //expected-note {{non-throwing function declare here}} + throw 2; // expected-warning {{has a non-throwing exception specification but}} +} catch (char *) { +} + +int main() { + R1 o; //expected-note {{in instantiation of member function}} + S1 b; //expected-note {{in instantiation of member function}} + dependent_warn f; + dependent_warn_noexcept f1; //expected-note {{in instantiation of member function}} + dependent_warn_both f2; + dependent_warn_both f3; //expected-note {{in instantiation of member function}} +}