Add an unreachable code checker.

llvm-svn: 93287
This commit is contained in:
Mike Stump 2010-01-13 02:59:54 +00:00
parent d7d8f6d000
commit 1bacb81d6f
6 changed files with 120 additions and 32 deletions

View File

@ -160,6 +160,8 @@ def warn_suggest_noreturn_function : Warning<
def warn_suggest_noreturn_block : Warning<
"block could be attribute 'noreturn'">,
InGroup<DiagGroup<"missing-noreturn">>, DefaultIgnore;
def warn_unreachable : Warning<"will never be executed">,
InGroup<DiagGroup<"unreachable-code">>, DefaultIgnore;
/// Built-in functions.
def ext_implicit_lib_function_decl : ExtWarn<

View File

@ -18,6 +18,7 @@
#include "clang/Analysis/CFG.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/ParentMap.h"
#include "clang/AST/StmtVisitor.h"
#include "clang/Analysis/Support/BumpVector.h"
@ -38,6 +39,9 @@ Stmt *AnalysisContext::getBody() {
return MD->getBody();
else if (const BlockDecl *BD = dyn_cast<BlockDecl>(D))
return BD->getBody();
else if (const FunctionTemplateDecl *FunTmpl
= dyn_cast_or_null<FunctionTemplateDecl>(D))
return FunTmpl->getTemplatedDecl()->getBody();
llvm_unreachable("unknown code decl");
}

View File

@ -42,6 +42,7 @@ namespace llvm {
}
namespace clang {
class AnalysisContext;
class ASTContext;
class ASTConsumer;
class CodeCompleteConsumer;
@ -1097,6 +1098,9 @@ public:
OwningExprResult BuildOverloadedArrowExpr(Scope *S, ExprArg Base,
SourceLocation OpLoc);
/// CheckUnreachable - Check for unreachable code.
void CheckUnreachable(AnalysisContext &);
/// CheckCallReturnType - Checks that a call expression's return type is
/// complete. Returns true on failure. The location passed in is the location
/// that best represents the call.
@ -1104,14 +1108,14 @@ public:
CallExpr *CE, FunctionDecl *FD);
/// Helpers for dealing with blocks and functions.
void CheckFallThroughForFunctionDef(Decl *D, Stmt *Body);
void CheckFallThroughForBlock(QualType BlockTy, Stmt *Body);
void CheckFallThroughForFunctionDef(Decl *D, Stmt *Body, AnalysisContext &);
void CheckFallThroughForBlock(QualType BlockTy, Stmt *, AnalysisContext &);
bool CheckParmsForFunctionDef(FunctionDecl *FD);
void CheckCXXDefaultArguments(FunctionDecl *FD);
void CheckExtraCXXDefaultArguments(Declarator &D);
enum ControlFlowKind { NeverFallThrough = 0, MaybeFallThrough = 1,
AlwaysFallThrough = 2, NeverFallThroughOrReturn = 3 };
ControlFlowKind CheckFallThrough(Stmt *);
ControlFlowKind CheckFallThrough(AnalysisContext &);
Scope *getNonFieldDeclScope(Scope *S);

View File

@ -14,6 +14,7 @@
#include "Sema.h"
#include "SemaInit.h"
#include "Lookup.h"
#include "clang/Analysis/PathSensitive/AnalysisContext.h"
#include "clang/AST/APValue.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/ASTContext.h"
@ -1305,30 +1306,10 @@ void Sema::MergeVarDecl(VarDecl *New, LookupResult &Previous) {
New->setPreviousDeclaration(Old);
}
/// CheckFallThrough - Check that we don't fall off the end of a
/// Statement that should return a value.
///
/// \returns AlwaysFallThrough iff we always fall off the end of the statement,
/// MaybeFallThrough iff we might or might not fall off the end,
/// NeverFallThroughOrReturn iff we never fall off the end of the statement or
/// return. We assume NeverFallThrough iff we never fall off the end of the
/// statement but we may return. We assume that functions not marked noreturn
/// will return.
Sema::ControlFlowKind Sema::CheckFallThrough(Stmt *Root) {
// FIXME: Eventually share this CFG object when we have other warnings based
// of the CFG. This can be done using AnalysisContext.
llvm::OwningPtr<CFG> cfg (CFG::buildCFG(Root, &Context));
// FIXME: They should never return 0, fix that, delete this code.
if (cfg == 0)
// FIXME: This should be NeverFallThrough
return NeverFallThroughOrReturn;
// The CFG leaves in dead things, and we don't want to dead code paths to
// confuse us, so we mark all live things first.
static void MarkLive(CFGBlock *e, llvm::BitVector &live) {
std::queue<CFGBlock*> workq;
llvm::BitVector live(cfg->getNumBlockIDs());
// Prep work queue
workq.push(&cfg->getEntry());
workq.push(e);
// Solve
while (!workq.empty()) {
CFGBlock *item = workq.front();
@ -1344,6 +1325,54 @@ Sema::ControlFlowKind Sema::CheckFallThrough(Stmt *Root) {
}
}
}
}
/// CheckUnreachable - Check for unreachable code.
void Sema::CheckUnreachable(AnalysisContext &AC) {
if (Diags.getDiagnosticLevel(diag::warn_unreachable) == Diagnostic::Ignored)
return;
CFG *cfg = AC.getCFG();
// FIXME: They should never return 0, fix that, delete this code.
if (cfg == 0)
return;
llvm::BitVector live(cfg->getNumBlockIDs());
// Mark all live things first.
MarkLive(&cfg->getEntry(), live);
for (unsigned i = 0; i < cfg->getNumBlockIDs(); ++i) {
if (!live[i]) {
CFGBlock &b = *(cfg->begin()[i]);
if (!b.empty())
Diag(b[0].getStmt()->getLocStart(), diag::warn_unreachable);
// Avoid excessive errors by marking everything reachable from here
MarkLive(&b, live);
}
}
}
/// CheckFallThrough - Check that we don't fall off the end of a
/// Statement that should return a value.
///
/// \returns AlwaysFallThrough iff we always fall off the end of the statement,
/// MaybeFallThrough iff we might or might not fall off the end,
/// NeverFallThroughOrReturn iff we never fall off the end of the statement or
/// return. We assume NeverFallThrough iff we never fall off the end of the
/// statement but we may return. We assume that functions not marked noreturn
/// will return.
Sema::ControlFlowKind Sema::CheckFallThrough(AnalysisContext &AC) {
CFG *cfg = AC.getCFG();
// FIXME: They should never return 0, fix that, delete this code.
if (cfg == 0)
// FIXME: This should be NeverFallThrough
return NeverFallThroughOrReturn;
// The CFG leaves in dead things, and we don't want to dead code paths to
// confuse us, so we mark all live things first.
std::queue<CFGBlock*> workq;
llvm::BitVector live(cfg->getNumBlockIDs());
MarkLive(&cfg->getEntry(), live);
// Now we know what is live, we check the live precessors of the exit block
// and look for fall through paths, being careful to ignore normal returns,
@ -1419,7 +1448,8 @@ Sema::ControlFlowKind Sema::CheckFallThrough(Stmt *Root) {
/// function that should return a value. Check that we don't fall off the end
/// of a noreturn function. We assume that functions and blocks not marked
/// noreturn will return.
void Sema::CheckFallThroughForFunctionDef(Decl *D, Stmt *Body) {
void Sema::CheckFallThroughForFunctionDef(Decl *D, Stmt *Body,
AnalysisContext &AC) {
// FIXME: Would be nice if we had a better way to control cascading errors,
// but for now, avoid them. The problem is that when Parse sees:
// int foo() { return a; }
@ -1457,7 +1487,7 @@ void Sema::CheckFallThroughForFunctionDef(Decl *D, Stmt *Body) {
return;
// FIXME: Function try block
if (CompoundStmt *Compound = dyn_cast<CompoundStmt>(Body)) {
switch (CheckFallThrough(Body)) {
switch (CheckFallThrough(AC)) {
case MaybeFallThrough:
if (HasNoReturn)
Diag(Compound->getRBracLoc(), diag::warn_falloff_noreturn_function);
@ -1484,7 +1514,8 @@ void Sema::CheckFallThroughForFunctionDef(Decl *D, Stmt *Body) {
/// that should return a value. Check that we don't fall off the end of a
/// noreturn block. We assume that functions and blocks not marked noreturn
/// will return.
void Sema::CheckFallThroughForBlock(QualType BlockTy, Stmt *Body) {
void Sema::CheckFallThroughForBlock(QualType BlockTy, Stmt *Body,
AnalysisContext &AC) {
// FIXME: Would be nice if we had a better way to control cascading errors,
// but for now, avoid them. The problem is that when Parse sees:
// int foo() { return a; }
@ -1510,7 +1541,7 @@ void Sema::CheckFallThroughForBlock(QualType BlockTy, Stmt *Body) {
return;
// FIXME: Funtion try block
if (CompoundStmt *Compound = dyn_cast<CompoundStmt>(Body)) {
switch (CheckFallThrough(Body)) {
switch (CheckFallThrough(AC)) {
case MaybeFallThrough:
if (HasNoReturn)
Diag(Compound->getRBracLoc(), diag::err_noreturn_block_has_return_expr);
@ -4330,6 +4361,7 @@ Sema::DeclPtrTy Sema::ActOnFinishFunctionBody(DeclPtrTy D, StmtArg BodyArg,
Decl *dcl = D.getAs<Decl>();
Stmt *Body = BodyArg.takeAs<Stmt>();
AnalysisContext AC(dcl);
FunctionDecl *FD = 0;
FunctionTemplateDecl *FunTmpl = dyn_cast_or_null<FunctionTemplateDecl>(dcl);
if (FunTmpl)
@ -4344,7 +4376,7 @@ Sema::DeclPtrTy Sema::ActOnFinishFunctionBody(DeclPtrTy D, StmtArg BodyArg,
// Implements C++ [basic.start.main]p5 and C99 5.1.2.2.3.
FD->setHasImplicitReturnZero(true);
else
CheckFallThroughForFunctionDef(FD, Body);
CheckFallThroughForFunctionDef(FD, Body, AC);
if (!FD->isInvalidDecl())
DiagnoseUnusedParameters(FD->param_begin(), FD->param_end());
@ -4356,7 +4388,7 @@ Sema::DeclPtrTy Sema::ActOnFinishFunctionBody(DeclPtrTy D, StmtArg BodyArg,
} else if (ObjCMethodDecl *MD = dyn_cast_or_null<ObjCMethodDecl>(dcl)) {
assert(MD == getCurMethodDecl() && "Method parsing confused");
MD->setBody(Body);
CheckFallThroughForFunctionDef(MD, Body);
CheckFallThroughForFunctionDef(MD, Body, AC);
MD->setEndLoc(Body->getLocEnd());
if (!MD->isInvalidDecl())
@ -4414,6 +4446,8 @@ Sema::DeclPtrTy Sema::ActOnFinishFunctionBody(DeclPtrTy D, StmtArg BodyArg,
if (!Body) return D;
CheckUnreachable(AC);
// Verify that that gotos and switch cases don't jump into scopes illegally.
if (CurFunctionNeedsScopeChecking)
DiagnoseInvalidJumps(Body);

View File

@ -14,6 +14,7 @@
#include "Sema.h"
#include "SemaInit.h"
#include "Lookup.h"
#include "clang/Analysis/PathSensitive/AnalysisContext.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/DeclTemplate.h"
@ -6859,7 +6860,9 @@ Sema::OwningExprResult Sema::ActOnBlockStmtExpr(SourceLocation CaretLoc,
CurFunctionNeedsScopeChecking = BSI->SavedFunctionNeedsScopeChecking;
BSI->TheDecl->setBody(body.takeAs<CompoundStmt>());
CheckFallThroughForBlock(BlockTy, BSI->TheDecl->getBody());
AnalysisContext AC(BSI->TheDecl);
CheckFallThroughForBlock(BlockTy, BSI->TheDecl->getBody(), AC);
CheckUnreachable(AC);
return Owned(new (Context) BlockExpr(BSI->TheDecl, BlockTy,
BSI->hasBlockDeclRefExprs));
}

View File

@ -0,0 +1,41 @@
// RUN: %clang_cc1 -fsyntax-only -Wunreachable-code -fblocks -verify %s
int j;
void bar() { }
int test1() {
for (int i = 0;
i != 10;
++i) { // expected-warning {{will never be executed}}
if (j == 23) // missing {}'s
bar();
return 1;
}
return 0;
return 1; // expected-warning {{will never be executed}}
}
void test2(int i) {
switch (i) {
case 0:
break;
bar(); // expected-warning {{will never be executed}}
case 2:
switch (i) {
default:
a: goto a;
}
bar(); // expected-warning {{will never be executed}}
}
b: goto b;
bar(); // expected-warning {{will never be executed}}
}
void test3() {
^{ return;
bar(); // expected-warning {{will never be executed}}
}();
while (++j) {
continue;
bar(); // expected-warning {{will never be executed}}
}
}