Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -6336,6 +6336,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, may result in unexpected program termination.">, + InGroup; +def note_throw_in_dtor + : Note<"destructor or deallocator has a (possible implicit) non-throwing " + "excepton specification">; +def note_throw_in_function + : Note<"nonthrowing 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,158 @@ } //===----------------------------------------------------------------------===// +// Check for throw in a non-throwing function. +//===----------------------------------------------------------------------===// +enum ThrowState { + FoundNoPathForThrow, + FoundPathForThrow, + FoundPathWithNoThrowOutFunction, +}; + +static bool mayThrowBeCaughted(const CXXThrowExpr *Throw, + const CXXCatchStmt *Catch) { + bool MayCaught = false; + const auto *ThrowType = Throw->getSubExpr()->getType().getTypePtrOrNull(); + const auto *CaughtType = Catch->getCaughtType().getTypePtrOrNull(); + + if (CaughtType == nullptr) + return true; + if (CaughtType == ThrowType) + return true; + + const auto *CaughtAsRecordType = CaughtType->getPointeeCXXRecordDecl(); + const auto *ThrowTypeAsRecordType = ThrowType->getAsCXXRecordDecl(); + if (CaughtAsRecordType && ThrowTypeAsRecordType) { + if (CaughtAsRecordType == ThrowTypeAsRecordType) + MayCaught = true; + MayCaught = ThrowTypeAsRecordType->isDerivedFrom(CaughtAsRecordType); + } + return MayCaught; +} + +static bool mayThrowBeCaughtedByHandlers(const CXXThrowExpr *CE, + const CXXTryStmt *TryStmt) { + bool Caught = false; + for (unsigned h = 0; h < TryStmt->getNumHandlers(); ++h) { + const CXXCatchStmt *CS = TryStmt->getHandler(h); + if (mayThrowBeCaughted(CE, CS)) { + Caught = true; + break; + } + } + return Caught; +} + +static bool hasThrowOutNothrowingFuncInPath(CFGBlock &Block, + SourceLocation *OpLoc) { + bool HasThrowOutFunc = false; + for (const auto &B : Block) { + if (B.getKind() != CFGElement::Statement) + continue; + const CXXThrowExpr *CE = + dyn_cast(B.getAs()->getStmt()); + if (!CE) + continue; + else + HasThrowOutFunc = true; + + (*OpLoc) = CE->getThrowLoc(); + for (CFGBlock::const_succ_iterator I = Block.succ_begin(), + E = Block.succ_end(); + I != E; ++I) { + if (*I && (*I)->getTerminator() && + isa((*I)->getTerminator())) { + const CXXTryStmt *Terminator = cast((*I)->getTerminator()); + if (mayThrowBeCaughtedByHandlers(CE, Terminator)) { + HasThrowOutFunc = false; + break; + } + } + } + } + return HasThrowOutFunc; +} + +static bool hasThrowOutNonThrowingFunc(const FunctionDecl *FD, + SourceLocation *OpLoc, CFG *cfg) { + + const unsigned ExitID = cfg->getExit().getBlockID(); + + SmallVector States(cfg->getNumBlockIDs(), + FoundNoPathForThrow); + States[cfg->getEntry().getBlockID()] = FoundPathWithNoThrowOutFunction; + + SmallVector Stack; + Stack.push_back(&cfg->getEntry()); + while (!Stack.empty()) { + CFGBlock *CurBlock = Stack.back(); + Stack.pop_back(); + + unsigned ID = CurBlock->getBlockID(); + ThrowState CurState = States[ID]; + if (CurState == FoundPathWithNoThrowOutFunction) { + // Found a path to the exit node without a throw exression. + if (ExitID == ID) + continue; + + if (hasThrowOutNothrowingFuncInPath(*CurBlock, OpLoc)) { + CurState = FoundPathForThrow; + } + } + + // Loop over successor blocks and add them to the Stack if their state + // changes. + + for (auto I = CurBlock->succ_begin(), E = CurBlock->succ_end(); I != E; ++I) + if (*I) { + unsigned next_ID = (*I)->getBlockID(); + if (next_ID == ExitID && CurState == FoundPathForThrow) { + States[next_ID] = CurState; + } else if (States[next_ID] < CurState) { + States[next_ID] = 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) { + 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 checkThrowInNonThrowingFucn(Sema &S, const FunctionDecl *FD, + AnalysisDeclContext &AC) { + CFG *cfg = AC.getCFG(); + if (!cfg) + return; + if (cfg->getExit().pred_empty()) + return; + SourceLocation OpLoc; + if (hasThrowOutNonThrowingFunc(FD, &OpLoc, cfg)) { + EmitDiagForCXXThrowInNonThrowingFunc(OpLoc, S, FD); + } +} + +static bool isNoexcept(const FunctionDecl *FD) { + if (const auto *FPT = FD->getType()->castAs()) + if (FPT->getExceptionSpecType() != EST_None && + FPT->isNothrow(FD->getASTContext())) + return true; + return false; +} + +//===----------------------------------------------------------------------===// // Check for missing return value. //===----------------------------------------------------------------------===// @@ -2127,6 +2279,14 @@ } } + //Check for throw out in 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)) + checkThrowInNonThrowingFucn(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 {{nonthrowing 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 {{nonthrowing 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,202 @@ +// 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 {{nonthrowing 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 {{nonthrowing function declare here}} + try { + throw 12; // expected-warning {{has a non-throwing exception specification but}} + } catch (const char *) { + } +} + +void i() noexcept { //expected-note {{nonthrowing function declare here}} + try { + throw 12; + } catch (int) { + throw; // expected-warning {{has a non-throwing exception specification but}} + } +} +void j() noexcept { //expected-note {{nonthrowing function declare here}} + try { + throw 12; + } catch (int) { + throw "haha"; // expected-warning {{has a non-throwing exception specification but}} + } +} + +void k() noexcept { //expected-note {{nonthrowing function declare here}} + try { + throw 12; + } catch (...) { + throw; // expected-warning {{has a non-throwing exception specification but}} + } +} + +void loo(int i) noexcept { //expected-note {{nonthrowing function declare here}} + if (i) + try { + throw 12; + } catch (int) { + throw "haha"; //expected-warning {{has a non-throwing exception specification but}} + } + i = 10; +} + +#define NOEXCEPT noexcept +void with_macro() NOEXCEPT { //expected-note {{nonthrowing function declare here}} + throw 1; // expected-warning {{has a non-throwing exception specification but}} +} + +void with_try_block() try { + throw 2; +} catch (...) { +} + +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}} +}