Skip to content

Commit

Permalink
Emit warning when throw exception in destruct or dealloc functions wh…
Browse files Browse the repository at this point in the history
…ich has a

(possible implicit) noexcept specifier

Throwing in the destructor is not good (C++11 change try to not allow see below).
 But in reality, those codes are exist.
C++11 [class.dtor]p3:

A declaration of a destructor that does not have an exception-specification is 
implicitly considered to have the same exception specification as an implicit 
declaration.

With this change, the application worked before may now run into runtime 
termination. My goal here is to emit a warning to provide only possible info to 
where the code may need to be changed.

First there is no way, in compile time to identify the “throw” really throw out 
of the function. Things like the call which throw out… To keep this simple, 
when “throw” is seen, checking its enclosing function(only destructor and 
dealloc functions) with noexcept(true) specifier emit warning.

Here is implementation detail:
A new member function CheckCXXThrowInNonThrowingFunc is added for class Sema 
in Sema.h. It is used in the call to both BuildCXXThrow and 
TransformCXXThrowExpr.

The function basic check if the enclosing function with non-throwing noexcept 
specifer, if so emit warning for it.

The example of warning message like:
k1.cpp:18:3: warning: ''~dependent_warn'' has a (possible implicit) non-throwing

    noexcept specifier. Throwing exception may cause termination.
        [-Wthrow-in-dtor]
        throw 1;
        ^

        k1.cpp:43:30: note: in instantiation of member function

        'dependent_warn<noexcept_fun>::~dependent_warn' requested here

        dependent_warn<noexcept_fun> f; // cause warning

Differential Revision: https://reviews.llvm.org/D33333

llvm-svn: 306149
  • Loading branch information
Erich Keane committed Jun 23, 2017
1 parent 6c3e41b commit 89fe9c2
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 5 deletions.
9 changes: 9 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Expand Up @@ -6351,6 +6351,15 @@ def err_exceptions_disabled : Error<
"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<Exceptions>;
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<
Expand Down
150 changes: 150 additions & 0 deletions clang/lib/Sema/AnalysisBasedWarnings.cpp
Expand Up @@ -278,6 +278,150 @@ static void checkRecursiveFunction(Sema &S, const FunctionDecl *FD,
S.Diag(Body->getLocStart(), diag::warn_infinite_recursive_function);
}

//===----------------------------------------------------------------------===//
// Check for throw in a non-throwing function.
//===----------------------------------------------------------------------===//
enum ThrowState {
FoundNoPathForThrow,
FoundPathForThrow,
FoundPathWithNoThrowOutFunction,
};

static bool isThrowCaught(const CXXThrowExpr *Throw,
const CXXCatchStmt *Catch) {
const Type *ThrowType = nullptr;
if (Throw->getSubExpr())
ThrowType = Throw->getSubExpr()->getType().getTypePtrOrNull();
if (!ThrowType)
return false;
const Type *CaughtType = Catch->getCaughtType().getTypePtrOrNull();
if (!CaughtType)
return true;
if (ThrowType->isReferenceType())
ThrowType = ThrowType->castAs<ReferenceType>()
->getPointeeType()
->getUnqualifiedDesugaredType();
if (CaughtType->isReferenceType())
CaughtType = CaughtType->castAs<ReferenceType>()
->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 auto *CE = dyn_cast<CXXThrowExpr>(B.getAs<CFGStmt>()->getStmt());
if (!CE)
continue;

OpLoc = CE->getThrowLoc();
for (const auto &I : Block.succs()) {
if (!I.isReachable())
continue;
if (const auto *Terminator =
dyn_cast_or_null<CXXTryStmt>(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<ThrowState, 16> States(BodyCFG->getNumBlockIDs(),
FoundNoPathForThrow);
States[BodyCFG->getEntry().getBlockID()] = FoundPathWithNoThrowOutFunction;

SmallVector<CFGBlock *, 16> 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(Sema &S, SourceLocation OpLoc,
const FunctionDecl *FD) {
if (!S.getSourceManager().isInSystemHeader(OpLoc)) {
S.Diag(OpLoc, diag::warn_throw_in_noexcept_func) << FD;
if (S.getLangOpts().CPlusPlus11 &&
(isa<CXXDestructorDecl>(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(S, OpLoc, FD);
}

static bool isNoexcept(const FunctionDecl *FD) {
const auto *FPT = FD->getType()->castAs<FunctionProtoType>();
if (FPT->getExceptionSpecType() != EST_None &&
FPT->isNothrow(FD->getASTContext()))
return true;
return false;
}

//===----------------------------------------------------------------------===//
// Check for missing return value.
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -2127,6 +2271,12 @@ AnalysisBasedWarnings::IssueWarnings(sema::AnalysisBasedWarnings::Policy P,
}
}

// 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<FunctionDecl>(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,
Expand Down
9 changes: 4 additions & 5 deletions clang/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
Expand Down

0 comments on commit 89fe9c2

Please sign in to comment.