Untitled diff
58 removals
255 lines
30 additions
223 lines
//===-- SimpleStreamChecker.cpp -----------------------------------------*- C++ -*--//
//===-- SimpleStreamChecker.cpp -----------------------------------------*- C++ -*--//
//
//
// The LLVM Compiler Infrastructure
// The LLVM Compiler Infrastructure
//
//
// This file is distributed under the University of Illinois Open Source
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
// License. See LICENSE.TXT for details.
//
//
//===----------------------------------------------------------------------===//
//===----------------------------------------------------------------------===//
//
//
// Defines a checker for proper use of fopen/fclose APIs.
// Defines a checker for proper use of fopen/fclose APIs.
// - If a file has been closed with fclose, it should not be accessed again.
// - If a file has been closed with fclose, it should not be accessed again.
// Accessing a closed file results in undefined behavior.
// Accessing a closed file results in undefined behavior.
// - If a file was opened with fopen, it must be closed with fclose before
// - If a file was opened with fopen, it must be closed with fclose before
// the execution ends. Failing to do so results in a resource leak.
// the execution ends. Failing to do so results in a resource leak.
//
//
//===----------------------------------------------------------------------===//
//===----------------------------------------------------------------------===//
#include "ClangSACheckers.h"
#include "ClangSACheckers.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
using namespace clang;
using namespace clang;
using namespace ento;
using namespace ento;
namespace {
namespace {
enum SimpleStreamState { Closed, Opened };
enum SimpleStreamState { Closed, Opened };
SmartStateTrait SimpleStreamTrait;
SmartStateTrait SimpleStreamTrait;
typedef SmallVector<SymbolRef, 2> SymbolVector;
typedef SmallVector<const MemRegion *, 2> RegionVector;
class SimpleStreamModel
class SimpleStreamModel
: public Checker<check::ASTDecl<TranslationUnitDecl>, check::PostCall,
: public Checker<check::ASTDecl<TranslationUnitDecl>, check::PostCall> {
check::PointerEscape> {
CallDescription OpenFn, CloseFn;
CallDescription OpenFn, CloseFn;
bool guaranteedNotToCloseFile(const CallEvent &Call) const;
bool guaranteedNotToCloseFile(const CallEvent &Call) const;
public:
public:
SimpleStreamModel() : OpenFn("fopen"), CloseFn("fclose") {}
SimpleStreamModel() : OpenFn("fopen"), CloseFn("fclose") {}
void checkASTDecl(const TranslationUnitDecl *D, AnalysisManager &AMgr,
void checkASTDecl(const TranslationUnitDecl *D, AnalysisManager &AMgr,
BugReporter &BR) const;
BugReporter &BR) const;
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
ProgramStateRef checkPointerEscape(ProgramStateRef State,
ProgramStateRef checkPointerEscape(ProgramStateRef State,
const InvalidatedSymbols &Escaped,
const InvalidatedSymbols &Escaped,
const CallEvent *Call,
const CallEvent *Call,
PointerEscapeKind Kind) const;
PointerEscapeKind Kind) const;
};
};
} // end of anonymous namespace
} // end of anonymous namespace
bool SimpleStreamModel::guaranteedNotToCloseFile(
const CallEvent &Call) const {
// If it's not in a system header, assume it might close a file.
if (!Call.isInSystemHeader())
return false;
// Handle cases where we know a buffer's /address/ can escape.
if (Call.argumentsMayEscape())
return false;
// Note, even though fclose closes the file, we do not list it here
// since the checker is modeling the call.
return true;
}
void SimpleStreamModel::checkASTDecl(const TranslationUnitDecl *D,
void SimpleStreamModel::checkASTDecl(const TranslationUnitDecl *D,
AnalysisManager &AMgr,
AnalysisManager &AMgr,
BugReporter &BR) const {
BugReporter &BR) const {
// Once the AST context is available, we can express the fact that our trait
// Once the AST context is available, we can express the fact that our trait
// has type 'int'.
// has type 'int'.
// FIXME: This is ugly. Maybe make a separate callback, or delay registering
// FIXME: This is ugly. Maybe make a separate callback, or delay registering
// checkers until AST is constructed?
// checkers until AST is constructed?
SimpleStreamTrait.initialize("SimpleStream", AMgr.getASTContext().IntTy);
SimpleStreamTrait.initialize("SimpleStream", AMgr.getASTContext().IntTy);
}
}
void SimpleStreamModel::checkPostCall(const CallEvent &Call,
void SimpleStreamModel::checkPostCall(const CallEvent &Call,
CheckerContext &C) const {
CheckerContext &C) const {
if (!Call.isGlobalCFunction())
if (!Call.isGlobalCFunction())
return;
return;
if (Call.isCalled(OpenFn)) {
if (Call.isCalled(OpenFn)) {
// Get the symbolic value corresponding to the file handle.
// Get the symbolic value corresponding to the file handle.
SymbolRef FileDesc = Call.getReturnValue().getAsSymbol();
const MemRegion *FileDesc = Call.getReturnValue().getAsRegion();
if (!FileDesc)
if (!FileDesc)
return;
return;
// Generate the next transition (an edge in the exploded graph).
// Generate the next transition (an edge in the exploded graph).
ProgramStateRef State = C.getState();
ProgramStateRef State = C.getState();
State = State->bindLoc(SimpleStreamTrait, FileDesc, Opened);
State = State->bindLoc(SimpleStreamTrait, FileDesc, Opened);
C.addTransition(State);
C.addTransition(State);
} else if (Call.isCalled(CloseFn)) {
} else if (Call.isCalled(CloseFn)) {
// Get the symbolic value corresponding to the file handle.
// Get the symbolic value corresponding to the file handle.
SymbolRef FileDesc = Call.getArgSVal(0).getAsSymbol();
const MemRegion *FileDesc = Call.getArgSVal(0).getAsRegion();
if (!FileDesc)
if (!FileDesc)
return;
return;
// Generate the next transition, in which the stream is closed.
// Generate the next transition, in which the stream is closed.
ProgramStateRef State = C.getState();
ProgramStateRef State = C.getState();
State = State->bindLoc(SimpleStreamTrait, FileDesc, Closed);
State = State->bindLoc(SimpleStreamTrait, FileDesc, Closed);
C.addTransition(State);
C.addTransition(State);
}
}
}
}
ProgramStateRef SimpleStreamModel::checkPointerEscape(
ProgramStateRef State, const InvalidatedSymbols &Escaped,
const CallEvent *Call, PointerEscapeKind Kind) const {
// If we know that the call cannot close a file, there is nothing to do.
if (Kind == PSK_DirectEscapeOnCall && guaranteedNotToCloseFile(*Call))
return State;
for (auto I = Escaped.begin(), E = Escaped.end(); I != E; ++I) {
SymbolRef Sym = *I;
// The symbol escaped. Optimistically, assume that the corresponding file
// handle will be closed somewhere else.
// FIXME: We're doing double lookup here, and we could have probably just
// deleted the binding, because our checker doesn't discriminate between
// different kind of unknowns.
if (State->hasAnyBinding(SimpleStreamTrait, Sym))
State = State->bindLoc(SimpleStreamTrait, Sym, UnknownVal());
}
return State;
}
namespace {
namespace {
class SimpleStreamChecker
class SimpleStreamChecker
: public Checker<check::PreCall, check::DeadSymbols> {
: public Checker<check::PreCall, check::DeadSymbols> {
CallDescription CloseFn;
CallDescription CloseFn;
std::unique_ptr<BugType> DoubleCloseBugType;
std::unique_ptr<BugType> DoubleCloseBugType;
std::unique_ptr<BugType> LeakBugType;
std::unique_ptr<BugType> LeakBugType;
void reportDoubleClose(SymbolRef FileDescSym,
void reportDoubleClose(const MemRegion *FileDescReg,
const CallEvent &Call,
const CallEvent &Call,
CheckerContext &C) const;
CheckerContext &C) const;
void reportLeaks(ArrayRef<SymbolRef> LeakedStreams, CheckerContext &C,
void reportLeaks(ArrayRef<const MemRegion *> LeakedStreams, CheckerContext &C,
ExplodedNode *ErrNode) const;
ExplodedNode *ErrNode) const;
public:
public:
SimpleStreamChecker();
SimpleStreamChecker();
/// Process fclose.
/// Process fclose.
void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
/// Detect leaks.
/// Detect leaks.
void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
};
};
} // end anonymous namespace
} // end anonymous namespace
SimpleStreamChecker::SimpleStreamChecker() : CloseFn("fclose", 1) {
SimpleStreamChecker::SimpleStreamChecker() : CloseFn("fclose", 1) {
// Initialize the bug types.
// Initialize the bug types.
DoubleCloseBugType.reset(
DoubleCloseBugType.reset(
new BugType(this, "Double fclose", "Unix Stream API Error"));
new BugType(this, "Double fclose", "Unix Stream API Error"));
LeakBugType.reset(
LeakBugType.reset(
new BugType(this, "Resource Leak", "Unix Stream API Error"));
new BugType(this, "Resource Leak", "Unix Stream API Error"));
// Sinks are higher importance bugs as well as calls to assert() or exit(0).
// Sinks are higher importance bugs as well as calls to assert() or exit(0).
LeakBugType->setSuppressOnSink(true);
LeakBugType->setSuppressOnSink(true);
}
}
void SimpleStreamChecker::checkPreCall(const CallEvent &Call,
void SimpleStreamChecker::checkPreCall(const CallEvent &Call,
CheckerContext &C) const {
CheckerContext &C) const {
if (!Call.isGlobalCFunction())
if (!Call.isGlobalCFunction())
return;
return;
if (!Call.isCalled(CloseFn))
if (!Call.isCalled(CloseFn))
return;
return;
// Get the symbolic value corresponding to the file handle.
// Get the symbolic value corresponding to the file handle.
SymbolRef FileDesc = Call.getArgSVal(0).getAsSymbol();
const MemRegion *FileDesc = Call.getArgSVal(0).getAsRegion();
if (!FileDesc)
if (!FileDesc)
return;
return;
// Check if the stream has already been closed.
// Check if the stream has already been closed.
ProgramStateRef State = C.getState();
ProgramStateRef State = C.getState();
SVal Val = State->getSVal(SimpleStreamTrait, FileDesc);
SVal Val = State->getSVal(SimpleStreamTrait, FileDesc);
if (Val.isConstant(Closed)) {
if (Val.isConstant(Closed)) {
reportDoubleClose(FileDesc, Call, C);
reportDoubleClose(FileDesc, Call, C);
return;
return;
}
}
}
}
static bool isLeaked(SymbolRef Sym, ProgramStateRef State) {
static bool isLeaked(const MemRegion *Reg, ProgramStateRef State) {
// If a symbol is NULL, assume that fopen failed on this path.
// If a symbol is NULL, assume that fopen failed on this path.
// A symbol should only be considered leaked if it is non-null.
// A symbol should only be considered leaked if it is non-null.
ConstraintManager &CMgr = State->getConstraintManager();
if (const SymbolicRegion *SR = Reg->getSymbolicBase()) {
ConditionTruthVal OpenFailed = CMgr.isNull(State, Sym);
SymbolRef Sym = SR->getSymbol();
return !OpenFailed.isConstrainedTrue();
ConstraintManager &CMgr = State->getConstraintManager();
ConditionTruthVal OpenFailed = CMgr.isNull(State, Sym);
return !OpenFailed.isConstrainedTrue();
}
return false;
}
}
void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
CheckerContext &C) const {
CheckerContext &C) const {
ProgramStateRef State = C.getState();
ProgramStateRef State = C.getState();
SymbolVector LeakedStreams;
RegionVector LeakedStreams;
// FIXME: Iterate through trait, not through dead symbols (might be faster)?
// FIXME: Iterate through trait, not through dead symbols (might be faster)?
for (auto I = SymReaper.dead_begin(), E = SymReaper.dead_end(); I != E; ++I) {
for (auto I = SymReaper.dead_begin(), E = SymReaper.dead_end(); I != E; ++I) {
SymbolRef FileDesc = *I;
// FIXME: This gets uglier with ghost fields.
const MemRegion *FileDesc = C.getSValBuilder().makeLoc(*I).getAsRegion();
SVal Val = State->getSVal(SimpleStreamTrait, FileDesc);
SVal Val = State->getSVal(SimpleStreamTrait, FileDesc);
if (!Val.isConstant(Opened))
if (!Val.isConstant(Opened))
continue;
continue;
// Collect leaked symbols.
// Collect leaked symbols.
if (isLeaked(FileDesc, State))
if (isLeaked(FileDesc, State))
LeakedStreams.push_back(FileDesc);
LeakedStreams.push_back(FileDesc);
}
}
if (LeakedStreams.empty())
if (LeakedStreams.empty())
return;
return;
ExplodedNode *N = C.generateNonFatalErrorNode(State);
ExplodedNode *N = C.generateNonFatalErrorNode(State);
if (!N)
if (!N)
return;
return;
reportLeaks(LeakedStreams, C, N);
reportLeaks(LeakedStreams, C, N);
}
}
void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym,
void SimpleStreamChecker::reportDoubleClose(const MemRegion *FileDescReg,
const CallEvent &Call,
const CallEvent &Call,
CheckerContext &C) const {
CheckerContext &C) const {
// We reached a bug, stop exploring the path here by generating a sink.
// We reached a bug, stop exploring the path here by generating a sink.
ExplodedNode *ErrNode = C.generateErrorNode();
ExplodedNode *ErrNode = C.generateErrorNode();
// If we've already reached this node on another path, return.
// If we've already reached this node on another path, return.
if (!ErrNode)
if (!ErrNode)
return;
return;
// Generate the report.
// Generate the report.
auto R = llvm::make_unique<BugReport>(*DoubleCloseBugType,
auto R = llvm::make_unique<BugReport>(*DoubleCloseBugType,
"Closing a previously closed file stream", ErrNode);
"Closing a previously closed file stream", ErrNode);
R->addRange(Call.getSourceRange());
R->addRange(Call.getSourceRange());
R->markInteresting(FileDescSym);
C.emitReport(std::move(R));
C.emitReport(std::move(R));
}
}
void SimpleStreamChecker::reportLeaks(ArrayRef<SymbolRef> LeakedStreams,
void SimpleStreamChecker::reportLeaks(ArrayRef<const MemRegion *> LeakedStreams,
CheckerContext &C,
CheckerContext &C,
ExplodedNode *ErrNode) const {
ExplodedNode *ErrNode) const {
// Attach bug reports to the leak node.
// Attach bug reports to the leak node.
// TODO: Identify the leaked file descriptor.
// TODO: Identify the leaked file descriptor.
for (SymbolRef LeakedStream : LeakedStreams) {
for (const MemRegion *LeakedStream : LeakedStreams) {
auto R = llvm::make_unique<BugReport>(*LeakBugType,
auto R = llvm::make_unique<BugReport>(*LeakBugType,
"Opened file is never closed; potential resource leak", ErrNode);
"Opened file is never closed; potential resource leak", ErrNode);
R->markInteresting(LeakedStream);
if (const SymbolicRegion *SR = LeakedStream->getSymbolicBase())
R->markInteresting(SR->getSymbol());
C.emitReport(std::move(R));
C.emitReport(std::move(R));
}
}
}
}
void ento::registerSimpleStreamCheckerV2(CheckerManager &mgr) {
void ento::registerSimpleStreamCheckerV3(CheckerManager &mgr) {
mgr.registerChecker<SimpleStreamModel>();
mgr.registerChecker<SimpleStreamModel>();
mgr.registerChecker<SimpleStreamChecker>();
mgr.registerChecker<SimpleStreamChecker>();
}
}