Implement CFG construction for __try / __except / __leave.

This makes -Wunreachable-code work for programs containing SEH (except for
__finally, which is still missing for now).

__try is modeled like try (but simpler since it can only have a single __except
or __finally), __except is fairly similar to catch (but simpler, since it can't
contain declarations). __leave is implemented similarly to break / continue.

Use the existing addTryDispatchBlock infrastructure (which
FindUnreachableCode() in ReachableCode.cpp uses via cfg->try_blocks_begin()) to
mark things in the __except blocks as reachable.

Re-use TryTerminatedBlock. This means we add EH edges from calls to the __try
block, but not from all other statements. While this is incomplete, it matches
LLVM's SEH codegen support. Also, in practice, BuildOpts.AddEHEdges is always
false in practice from what I can tell, so we never even insert the call EH
edges either.

https://reviews.llvm.org/D36914


git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@311561 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
Nico Weber 2017-08-23 15:33:16 +00:00
parent d2a520ee9e
commit 7ed95d9236
2 changed files with 203 additions and 10 deletions

View File

@ -395,12 +395,16 @@ class CFGBuilder {
ASTContext *Context;
std::unique_ptr<CFG> cfg;
CFGBlock *Block;
CFGBlock *Succ;
CFGBlock *Block; // Current block.
CFGBlock *Succ; // Block after the current block.
JumpTarget ContinueJumpTarget;
JumpTarget BreakJumpTarget;
JumpTarget SEHLeaveJumpTarget;
CFGBlock *SwitchTerminatedBlock;
CFGBlock *DefaultCaseBlock;
// This can point either to a try or a __try block. The frontend forbids
// mixing both kinds in one function, so having one for both is enough.
CFGBlock *TryTerminatedBlock;
// Current position in local scope.
@ -436,13 +440,12 @@ class CFGBuilder {
public:
explicit CFGBuilder(ASTContext *astContext,
const CFG::BuildOptions &buildOpts)
: Context(astContext), cfg(new CFG()), // crew a new CFG
Block(nullptr), Succ(nullptr),
SwitchTerminatedBlock(nullptr), DefaultCaseBlock(nullptr),
TryTerminatedBlock(nullptr), badCFG(false), BuildOpts(buildOpts),
switchExclusivelyCovered(false), switchCond(nullptr),
cachedEntry(nullptr), lastLookup(nullptr) {}
const CFG::BuildOptions &buildOpts)
: Context(astContext), cfg(new CFG()), // crew a new CFG
Block(nullptr), Succ(nullptr), SwitchTerminatedBlock(nullptr),
DefaultCaseBlock(nullptr), TryTerminatedBlock(nullptr), badCFG(false),
BuildOpts(buildOpts), switchExclusivelyCovered(false),
switchCond(nullptr), cachedEntry(nullptr), lastLookup(nullptr) {}
// buildCFG - Used by external clients to construct the CFG.
std::unique_ptr<CFG> buildCFG(const Decl *D, Stmt *Statement);
@ -501,6 +504,10 @@ private:
CFGBlock *VisitObjCForCollectionStmt(ObjCForCollectionStmt *S);
CFGBlock *VisitPseudoObjectExpr(PseudoObjectExpr *E);
CFGBlock *VisitReturnStmt(ReturnStmt *R);
CFGBlock *VisitSEHExceptStmt(SEHExceptStmt *S);
CFGBlock *VisitSEHFinallyStmt(SEHFinallyStmt *S);
CFGBlock *VisitSEHLeaveStmt(SEHLeaveStmt *S);
CFGBlock *VisitSEHTryStmt(SEHTryStmt *S);
CFGBlock *VisitStmtExpr(StmtExpr *S, AddStmtChoice asc);
CFGBlock *VisitSwitchStmt(SwitchStmt *S);
CFGBlock *VisitUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *E,
@ -1731,6 +1738,18 @@ CFGBlock *CFGBuilder::Visit(Stmt * S, AddStmtChoice asc) {
case Stmt::ReturnStmtClass:
return VisitReturnStmt(cast<ReturnStmt>(S));
case Stmt::SEHExceptStmtClass:
return VisitSEHExceptStmt(cast<SEHExceptStmt>(S));
case Stmt::SEHFinallyStmtClass:
return VisitSEHFinallyStmt(cast<SEHFinallyStmt>(S));
case Stmt::SEHLeaveStmtClass:
return VisitSEHLeaveStmt(cast<SEHLeaveStmt>(S));
case Stmt::SEHTryStmtClass:
return VisitSEHTryStmt(cast<SEHTryStmt>(S));
case Stmt::UnaryExprOrTypeTraitExprClass:
return VisitUnaryExprOrTypeTraitExpr(cast<UnaryExprOrTypeTraitExpr>(S),
asc);
@ -2462,6 +2481,117 @@ CFGBlock *CFGBuilder::VisitReturnStmt(ReturnStmt *R) {
return VisitStmt(R, AddStmtChoice::AlwaysAdd);
}
CFGBlock *CFGBuilder::VisitSEHExceptStmt(SEHExceptStmt *ES) {
// SEHExceptStmt are treated like labels, so they are the first statement in a
// block.
// Save local scope position because in case of exception variable ScopePos
// won't be restored when traversing AST.
SaveAndRestore<LocalScope::const_iterator> save_scope_pos(ScopePos);
addStmt(ES->getBlock());
CFGBlock *SEHExceptBlock = Block;
if (!SEHExceptBlock)
SEHExceptBlock = createBlock();
appendStmt(SEHExceptBlock, ES);
// Also add the SEHExceptBlock as a label, like with regular labels.
SEHExceptBlock->setLabel(ES);
// Bail out if the CFG is bad.
if (badCFG)
return nullptr;
// We set Block to NULL to allow lazy creation of a new block (if necessary).
Block = nullptr;
return SEHExceptBlock;
}
CFGBlock *CFGBuilder::VisitSEHFinallyStmt(SEHFinallyStmt *FS) {
return VisitCompoundStmt(FS->getBlock());
}
CFGBlock *CFGBuilder::VisitSEHLeaveStmt(SEHLeaveStmt *LS) {
// "__leave" is a control-flow statement. Thus we stop processing the current
// block.
if (badCFG)
return nullptr;
// Now create a new block that ends with the __leave statement.
Block = createBlock(false);
Block->setTerminator(LS);
// If there is no target for the __leave, then we are looking at an incomplete
// AST. This means that the CFG cannot be constructed.
if (SEHLeaveJumpTarget.block) {
addAutomaticObjHandling(ScopePos, SEHLeaveJumpTarget.scopePosition, LS);
addSuccessor(Block, SEHLeaveJumpTarget.block);
} else
badCFG = true;
return Block;
}
CFGBlock *CFGBuilder::VisitSEHTryStmt(SEHTryStmt *Terminator) {
// "__try"/"__except"/"__finally" is a control-flow statement. Thus we stop
// processing the current block.
CFGBlock *SEHTrySuccessor = nullptr;
if (Block) {
if (badCFG)
return nullptr;
SEHTrySuccessor = Block;
} else SEHTrySuccessor = Succ;
// FIXME: Implement __finally support.
if (Terminator->getFinallyHandler())
return NYS();
CFGBlock *PrevSEHTryTerminatedBlock = TryTerminatedBlock;
// Create a new block that will contain the __try statement.
CFGBlock *NewTryTerminatedBlock = createBlock(false);
// Add the terminator in the __try block.
NewTryTerminatedBlock->setTerminator(Terminator);
if (SEHExceptStmt *Except = Terminator->getExceptHandler()) {
// The code after the try is the implicit successor if there's an __except.
Succ = SEHTrySuccessor;
Block = nullptr;
CFGBlock *ExceptBlock = VisitSEHExceptStmt(Except);
if (!ExceptBlock)
return nullptr;
// Add this block to the list of successors for the block with the try
// statement.
addSuccessor(NewTryTerminatedBlock, ExceptBlock);
}
if (PrevSEHTryTerminatedBlock)
addSuccessor(NewTryTerminatedBlock, PrevSEHTryTerminatedBlock);
else
addSuccessor(NewTryTerminatedBlock, &cfg->getExit());
// The code after the try is the implicit successor.
Succ = SEHTrySuccessor;
// Save the current "__try" context.
SaveAndRestore<CFGBlock *> save_try(TryTerminatedBlock,
NewTryTerminatedBlock);
cfg->addTryDispatchBlock(TryTerminatedBlock);
// Save the current value for the __leave target.
// All __leaves should go to the code following the __try
// (FIXME: or if the __try has a __finally, to the __finally.)
SaveAndRestore<JumpTarget> save_break(SEHLeaveJumpTarget);
SEHLeaveJumpTarget = JumpTarget(SEHTrySuccessor, ScopePos);
assert(Terminator->getTryBlock() && "__try must contain a non-NULL body");
Block = nullptr;
return addStmt(Terminator->getTryBlock());
}
CFGBlock *CFGBuilder::VisitLabelStmt(LabelStmt *L) {
// Get the block of the labeled statement. Add it to our map.
addStmt(L->getSubStmt());
@ -4323,6 +4453,10 @@ public:
OS << "try ...";
}
void VisitSEHTryStmt(SEHTryStmt *CS) {
OS << "__try ...";
}
void VisitAbstractConditionalOperator(AbstractConditionalOperator* C) {
if (Stmt *Cond = C->getCond())
Cond->printPretty(OS, Helper, Policy);
@ -4555,7 +4689,11 @@ static void print_block(raw_ostream &OS, const CFG* cfg,
else
OS << "...";
OS << ")";
} else if (SEHExceptStmt *ES = dyn_cast<SEHExceptStmt>(Label)) {
OS << "__except (";
ES->getFilterExpr()->printPretty(OS, &Helper,
PrintingPolicy(Helper.getLangOpts()), 0);
OS << ")";
} else
llvm_unreachable("Invalid label statement in CFGBlock.");

View File

@ -0,0 +1,55 @@
// RUN: %clang_cc1 %s -triple=i686-pc-win32 -fsyntax-only -verify -fms-extensions -Wunreachable-code
void f();
void g1() {
__try {
f();
__leave;
f(); // expected-warning{{will never be executed}}
} __except(1) {
f();
}
// Completely empty.
__try {
} __except(1) {
}
__try {
f();
return;
} __except(1) { // Filter expression should not be marked as unreachable.
// Empty __except body.
}
}
void g2() {
__try {
// Nested __try.
__try {
f();
__leave;
f(); // expected-warning{{will never be executed}}
} __except(2) {
}
f();
__leave;
f(); // expected-warning{{will never be executed}}
} __except(1) {
f();
}
}
void g3() {
__try {
__try {
f();
} __except (1) {
__leave; // should exit outer try
}
__leave;
f(); // expected-warning{{never be executed}}
} __except (1) {
}
}