llvm-project/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp

412 lines
13 KiB
C++
Raw Normal View History

//===-- StreamChecker.cpp -----------------------------------------*- C++ -*--//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file defines checkers that model and check stream handling functions.
//
//===----------------------------------------------------------------------===//
#include "ClangSACheckers.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
using namespace clang;
using namespace ento;
namespace {
struct StreamState {
enum Kind { Opened, Closed, OpenFailed, Escaped } K;
const Stmt *S;
StreamState(Kind k, const Stmt *s) : K(k), S(s) {}
bool isOpened() const { return K == Opened; }
bool isClosed() const { return K == Closed; }
//bool isOpenFailed() const { return K == OpenFailed; }
//bool isEscaped() const { return K == Escaped; }
bool operator==(const StreamState &X) const {
return K == X.K && S == X.S;
}
static StreamState getOpened(const Stmt *s) { return StreamState(Opened, s); }
static StreamState getClosed(const Stmt *s) { return StreamState(Closed, s); }
static StreamState getOpenFailed(const Stmt *s) {
return StreamState(OpenFailed, s);
}
static StreamState getEscaped(const Stmt *s) {
return StreamState(Escaped, s);
}
void Profile(llvm::FoldingSetNodeID &ID) const {
ID.AddInteger(K);
ID.AddPointer(S);
}
};
class StreamChecker : public Checker<eval::Call,
check::DeadSymbols > {
mutable IdentifierInfo *II_fopen, *II_tmpfile, *II_fclose, *II_fread,
*II_fwrite,
*II_fseek, *II_ftell, *II_rewind, *II_fgetpos, *II_fsetpos,
*II_clearerr, *II_feof, *II_ferror, *II_fileno;
mutable std::unique_ptr<BuiltinBug> BT_nullfp, BT_illegalwhence,
BT_doubleclose, BT_ResourceLeak;
public:
StreamChecker()
: II_fopen(nullptr), II_tmpfile(nullptr), II_fclose(nullptr),
II_fread(nullptr), II_fwrite(nullptr), II_fseek(nullptr),
II_ftell(nullptr), II_rewind(nullptr), II_fgetpos(nullptr),
II_fsetpos(nullptr), II_clearerr(nullptr), II_feof(nullptr),
II_ferror(nullptr), II_fileno(nullptr) {}
bool evalCall(const CallExpr *CE, CheckerContext &C) const;
void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
private:
void Fopen(CheckerContext &C, const CallExpr *CE) const;
void Tmpfile(CheckerContext &C, const CallExpr *CE) const;
void Fclose(CheckerContext &C, const CallExpr *CE) const;
void Fread(CheckerContext &C, const CallExpr *CE) const;
void Fwrite(CheckerContext &C, const CallExpr *CE) const;
void Fseek(CheckerContext &C, const CallExpr *CE) const;
void Ftell(CheckerContext &C, const CallExpr *CE) const;
void Rewind(CheckerContext &C, const CallExpr *CE) const;
void Fgetpos(CheckerContext &C, const CallExpr *CE) const;
void Fsetpos(CheckerContext &C, const CallExpr *CE) const;
void Clearerr(CheckerContext &C, const CallExpr *CE) const;
void Feof(CheckerContext &C, const CallExpr *CE) const;
void Ferror(CheckerContext &C, const CallExpr *CE) const;
void Fileno(CheckerContext &C, const CallExpr *CE) const;
void OpenFileAux(CheckerContext &C, const CallExpr *CE) const;
ProgramStateRef CheckNullStream(SVal SV, ProgramStateRef state,
CheckerContext &C) const;
ProgramStateRef CheckDoubleClose(const CallExpr *CE, ProgramStateRef state,
CheckerContext &C) const;
};
} // end anonymous namespace
REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
bool StreamChecker::evalCall(const CallExpr *CE, CheckerContext &C) const {
const FunctionDecl *FD = C.getCalleeDecl(CE);
if (!FD || FD->getKind() != Decl::Function)
return false;
ASTContext &Ctx = C.getASTContext();
if (!II_fopen)
II_fopen = &Ctx.Idents.get("fopen");
if (!II_tmpfile)
II_tmpfile = &Ctx.Idents.get("tmpfile");
if (!II_fclose)
II_fclose = &Ctx.Idents.get("fclose");
if (!II_fread)
II_fread = &Ctx.Idents.get("fread");
if (!II_fwrite)
II_fwrite = &Ctx.Idents.get("fwrite");
if (!II_fseek)
II_fseek = &Ctx.Idents.get("fseek");
if (!II_ftell)
II_ftell = &Ctx.Idents.get("ftell");
if (!II_rewind)
II_rewind = &Ctx.Idents.get("rewind");
if (!II_fgetpos)
II_fgetpos = &Ctx.Idents.get("fgetpos");
if (!II_fsetpos)
II_fsetpos = &Ctx.Idents.get("fsetpos");
if (!II_clearerr)
II_clearerr = &Ctx.Idents.get("clearerr");
if (!II_feof)
II_feof = &Ctx.Idents.get("feof");
if (!II_ferror)
II_ferror = &Ctx.Idents.get("ferror");
if (!II_fileno)
II_fileno = &Ctx.Idents.get("fileno");
if (FD->getIdentifier() == II_fopen) {
Fopen(C, CE);
return true;
}
if (FD->getIdentifier() == II_tmpfile) {
Tmpfile(C, CE);
return true;
}
if (FD->getIdentifier() == II_fclose) {
Fclose(C, CE);
return true;
}
if (FD->getIdentifier() == II_fread) {
Fread(C, CE);
return true;
}
if (FD->getIdentifier() == II_fwrite) {
Fwrite(C, CE);
return true;
}
if (FD->getIdentifier() == II_fseek) {
Fseek(C, CE);
return true;
}
if (FD->getIdentifier() == II_ftell) {
Ftell(C, CE);
return true;
}
if (FD->getIdentifier() == II_rewind) {
Rewind(C, CE);
return true;
}
if (FD->getIdentifier() == II_fgetpos) {
Fgetpos(C, CE);
return true;
}
if (FD->getIdentifier() == II_fsetpos) {
Fsetpos(C, CE);
return true;
}
if (FD->getIdentifier() == II_clearerr) {
Clearerr(C, CE);
return true;
}
if (FD->getIdentifier() == II_feof) {
Feof(C, CE);
return true;
}
if (FD->getIdentifier() == II_ferror) {
Ferror(C, CE);
return true;
}
if (FD->getIdentifier() == II_fileno) {
Fileno(C, CE);
return true;
}
return false;
}
void StreamChecker::Fopen(CheckerContext &C, const CallExpr *CE) const {
OpenFileAux(C, CE);
}
void StreamChecker::Tmpfile(CheckerContext &C, const CallExpr *CE) const {
OpenFileAux(C, CE);
}
void StreamChecker::OpenFileAux(CheckerContext &C, const CallExpr *CE) const {
ProgramStateRef state = C.getState();
SValBuilder &svalBuilder = C.getSValBuilder();
const LocationContext *LCtx = C.getPredecessor()->getLocationContext();
DefinedSVal RetVal = svalBuilder.conjureSymbolVal(nullptr, CE, LCtx,
C.blockCount())
.castAs<DefinedSVal>();
state = state->BindExpr(CE, C.getLocationContext(), RetVal);
ConstraintManager &CM = C.getConstraintManager();
// Bifurcate the state into two: one with a valid FILE* pointer, the other
// with a NULL.
ProgramStateRef stateNotNull, stateNull;
std::tie(stateNotNull, stateNull) = CM.assumeDual(state, RetVal);
if (SymbolRef Sym = RetVal.getAsSymbol()) {
// if RetVal is not NULL, set the symbol's state to Opened.
stateNotNull =
stateNotNull->set<StreamMap>(Sym,StreamState::getOpened(CE));
stateNull =
stateNull->set<StreamMap>(Sym, StreamState::getOpenFailed(CE));
C.addTransition(stateNotNull);
C.addTransition(stateNull);
}
}
void StreamChecker::Fclose(CheckerContext &C, const CallExpr *CE) const {
ProgramStateRef state = CheckDoubleClose(CE, C.getState(), C);
if (state)
C.addTransition(state);
}
void StreamChecker::Fread(CheckerContext &C, const CallExpr *CE) const {
ProgramStateRef state = C.getState();
if (!CheckNullStream(C.getSVal(CE->getArg(3)), state, C))
return;
}
void StreamChecker::Fwrite(CheckerContext &C, const CallExpr *CE) const {
ProgramStateRef state = C.getState();
if (!CheckNullStream(C.getSVal(CE->getArg(3)), state, C))
return;
}
void StreamChecker::Fseek(CheckerContext &C, const CallExpr *CE) const {
ProgramStateRef state = C.getState();
if (!(state = CheckNullStream(C.getSVal(CE->getArg(0)), state, C)))
return;
// Check the legality of the 'whence' argument of 'fseek'.
SVal Whence = state->getSVal(CE->getArg(2), C.getLocationContext());
Optional<nonloc::ConcreteInt> CI = Whence.getAs<nonloc::ConcreteInt>();
if (!CI)
return;
int64_t x = CI->getValue().getSExtValue();
if (x >= 0 && x <= 2)
return;
[analyzer] Add generateErrorNode() APIs to CheckerContext. The analyzer trims unnecessary nodes from the exploded graph before reporting path diagnostics. However, in some cases it can trim all nodes (including the error node), leading to an assertion failure (see https://llvm.org/bugs/show_bug.cgi?id=24184). This commit addresses the issue by adding two new APIs to CheckerContext to explicitly create error nodes. Unless the client provides a custom tag, these APIs tag the node with the checker's tag -- preventing it from being trimmed. The generateErrorNode() method creates a sink error node, while generateNonFatalErrorNode() creates an error node for a path that should continue being explored. The intent is that one of these two methods should be used whenever a checker creates an error node. This commit updates the checkers to use these APIs. These APIs (unlike addTransition() and generateSink()) do not take an explicit Pred node. This is because there are not any error nodes in the checkers that were created with an explicit different than the default (the CheckerContext's Pred node). It also changes generateSink() to require state and pred nodes (previously these were optional) to reduce confusion. Additionally, there were several cases where checkers did check whether a generated node could be null; we now explicitly check for null in these places. This commit also includes a test case written by Ying Yi as part of http://reviews.llvm.org/D12163 (that patch originally addressed this issue but was reverted because it introduced false positive regressions). Differential Revision: http://reviews.llvm.org/D12780 llvm-svn: 247859
2015-09-17 06:03:05 +08:00
if (ExplodedNode *N = C.generateNonFatalErrorNode(state)) {
if (!BT_illegalwhence)
BT_illegalwhence.reset(
new BuiltinBug(this, "Illegal whence argument",
"The whence argument to fseek() should be "
"SEEK_SET, SEEK_END, or SEEK_CUR."));
C.emitReport(llvm::make_unique<BugReport>(
*BT_illegalwhence, BT_illegalwhence->getDescription(), N));
}
}
void StreamChecker::Ftell(CheckerContext &C, const CallExpr *CE) const {
ProgramStateRef state = C.getState();
if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C))
return;
}
void StreamChecker::Rewind(CheckerContext &C, const CallExpr *CE) const {
ProgramStateRef state = C.getState();
if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C))
return;
}
void StreamChecker::Fgetpos(CheckerContext &C, const CallExpr *CE) const {
ProgramStateRef state = C.getState();
if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C))
return;
}
void StreamChecker::Fsetpos(CheckerContext &C, const CallExpr *CE) const {
ProgramStateRef state = C.getState();
if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C))
return;
}
void StreamChecker::Clearerr(CheckerContext &C, const CallExpr *CE) const {
ProgramStateRef state = C.getState();
if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C))
return;
}
void StreamChecker::Feof(CheckerContext &C, const CallExpr *CE) const {
ProgramStateRef state = C.getState();
if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C))
return;
}
void StreamChecker::Ferror(CheckerContext &C, const CallExpr *CE) const {
ProgramStateRef state = C.getState();
if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C))
return;
}
void StreamChecker::Fileno(CheckerContext &C, const CallExpr *CE) const {
ProgramStateRef state = C.getState();
if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C))
return;
}
ProgramStateRef StreamChecker::CheckNullStream(SVal SV, ProgramStateRef state,
CheckerContext &C) const {
Optional<DefinedSVal> DV = SV.getAs<DefinedSVal>();
if (!DV)
return nullptr;
ConstraintManager &CM = C.getConstraintManager();
ProgramStateRef stateNotNull, stateNull;
std::tie(stateNotNull, stateNull) = CM.assumeDual(state, *DV);
if (!stateNotNull && stateNull) {
[analyzer] Add generateErrorNode() APIs to CheckerContext. The analyzer trims unnecessary nodes from the exploded graph before reporting path diagnostics. However, in some cases it can trim all nodes (including the error node), leading to an assertion failure (see https://llvm.org/bugs/show_bug.cgi?id=24184). This commit addresses the issue by adding two new APIs to CheckerContext to explicitly create error nodes. Unless the client provides a custom tag, these APIs tag the node with the checker's tag -- preventing it from being trimmed. The generateErrorNode() method creates a sink error node, while generateNonFatalErrorNode() creates an error node for a path that should continue being explored. The intent is that one of these two methods should be used whenever a checker creates an error node. This commit updates the checkers to use these APIs. These APIs (unlike addTransition() and generateSink()) do not take an explicit Pred node. This is because there are not any error nodes in the checkers that were created with an explicit different than the default (the CheckerContext's Pred node). It also changes generateSink() to require state and pred nodes (previously these were optional) to reduce confusion. Additionally, there were several cases where checkers did check whether a generated node could be null; we now explicitly check for null in these places. This commit also includes a test case written by Ying Yi as part of http://reviews.llvm.org/D12163 (that patch originally addressed this issue but was reverted because it introduced false positive regressions). Differential Revision: http://reviews.llvm.org/D12780 llvm-svn: 247859
2015-09-17 06:03:05 +08:00
if (ExplodedNode *N = C.generateErrorNode(stateNull)) {
if (!BT_nullfp)
BT_nullfp.reset(new BuiltinBug(this, "NULL stream pointer",
"Stream pointer might be NULL."));
C.emitReport(llvm::make_unique<BugReport>(
*BT_nullfp, BT_nullfp->getDescription(), N));
}
return nullptr;
}
return stateNotNull;
}
ProgramStateRef StreamChecker::CheckDoubleClose(const CallExpr *CE,
ProgramStateRef state,
CheckerContext &C) const {
SymbolRef Sym = C.getSVal(CE->getArg(0)).getAsSymbol();
if (!Sym)
return state;
const StreamState *SS = state->get<StreamMap>(Sym);
// If the file stream is not tracked, return.
if (!SS)
return state;
// Check: Double close a File Descriptor could cause undefined behaviour.
// Conforming to man-pages
if (SS->isClosed()) {
[analyzer] Add generateErrorNode() APIs to CheckerContext. The analyzer trims unnecessary nodes from the exploded graph before reporting path diagnostics. However, in some cases it can trim all nodes (including the error node), leading to an assertion failure (see https://llvm.org/bugs/show_bug.cgi?id=24184). This commit addresses the issue by adding two new APIs to CheckerContext to explicitly create error nodes. Unless the client provides a custom tag, these APIs tag the node with the checker's tag -- preventing it from being trimmed. The generateErrorNode() method creates a sink error node, while generateNonFatalErrorNode() creates an error node for a path that should continue being explored. The intent is that one of these two methods should be used whenever a checker creates an error node. This commit updates the checkers to use these APIs. These APIs (unlike addTransition() and generateSink()) do not take an explicit Pred node. This is because there are not any error nodes in the checkers that were created with an explicit different than the default (the CheckerContext's Pred node). It also changes generateSink() to require state and pred nodes (previously these were optional) to reduce confusion. Additionally, there were several cases where checkers did check whether a generated node could be null; we now explicitly check for null in these places. This commit also includes a test case written by Ying Yi as part of http://reviews.llvm.org/D12163 (that patch originally addressed this issue but was reverted because it introduced false positive regressions). Differential Revision: http://reviews.llvm.org/D12780 llvm-svn: 247859
2015-09-17 06:03:05 +08:00
ExplodedNode *N = C.generateErrorNode();
if (N) {
if (!BT_doubleclose)
BT_doubleclose.reset(new BuiltinBug(
this, "Double fclose", "Try to close a file Descriptor already"
" closed. Cause undefined behaviour."));
C.emitReport(llvm::make_unique<BugReport>(
*BT_doubleclose, BT_doubleclose->getDescription(), N));
}
return nullptr;
}
// Close the File Descriptor.
return state->set<StreamMap>(Sym, StreamState::getClosed(CE));
}
void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
CheckerContext &C) const {
// TODO: Clean up the state.
for (SymbolReaper::dead_iterator I = SymReaper.dead_begin(),
2010-08-22 09:00:03 +08:00
E = SymReaper.dead_end(); I != E; ++I) {
SymbolRef Sym = *I;
ProgramStateRef state = C.getState();
const StreamState *SS = state->get<StreamMap>(Sym);
if (!SS)
continue;
if (SS->isOpened()) {
[analyzer] Add generateErrorNode() APIs to CheckerContext. The analyzer trims unnecessary nodes from the exploded graph before reporting path diagnostics. However, in some cases it can trim all nodes (including the error node), leading to an assertion failure (see https://llvm.org/bugs/show_bug.cgi?id=24184). This commit addresses the issue by adding two new APIs to CheckerContext to explicitly create error nodes. Unless the client provides a custom tag, these APIs tag the node with the checker's tag -- preventing it from being trimmed. The generateErrorNode() method creates a sink error node, while generateNonFatalErrorNode() creates an error node for a path that should continue being explored. The intent is that one of these two methods should be used whenever a checker creates an error node. This commit updates the checkers to use these APIs. These APIs (unlike addTransition() and generateSink()) do not take an explicit Pred node. This is because there are not any error nodes in the checkers that were created with an explicit different than the default (the CheckerContext's Pred node). It also changes generateSink() to require state and pred nodes (previously these were optional) to reduce confusion. Additionally, there were several cases where checkers did check whether a generated node could be null; we now explicitly check for null in these places. This commit also includes a test case written by Ying Yi as part of http://reviews.llvm.org/D12163 (that patch originally addressed this issue but was reverted because it introduced false positive regressions). Differential Revision: http://reviews.llvm.org/D12780 llvm-svn: 247859
2015-09-17 06:03:05 +08:00
ExplodedNode *N = C.generateErrorNode();
if (N) {
2010-08-22 09:00:03 +08:00
if (!BT_ResourceLeak)
BT_ResourceLeak.reset(new BuiltinBug(
this, "Resource Leak",
"Opened File never closed. Potential Resource leak."));
C.emitReport(llvm::make_unique<BugReport>(
*BT_ResourceLeak, BT_ResourceLeak->getDescription(), N));
}
}
}
}
void ento::registerStreamChecker(CheckerManager &mgr) {
mgr.registerChecker<StreamChecker>();
}