Add double close check to StreamChecker. Patch by Lei Zhang.

llvm-svn: 108669
This commit is contained in:
Zhongxing Xu 2010-07-19 01:52:29 +00:00
parent 84f65e0692
commit ec5623570e
2 changed files with 94 additions and 4 deletions

View File

@ -23,18 +23,42 @@ using namespace clang;
namespace {
struct StreamState {
enum Kind { Opened, Closed, OpenFailed } 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 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); }
void Profile(llvm::FoldingSetNodeID &ID) const {
ID.AddInteger(K);
ID.AddPointer(S);
}
};
class StreamChecker : public CheckerVisitor<StreamChecker> {
IdentifierInfo *II_fopen, *II_fread, *II_fwrite,
IdentifierInfo *II_fopen, *II_fclose,*II_fread, *II_fwrite,
*II_fseek, *II_ftell, *II_rewind, *II_fgetpos, *II_fsetpos,
*II_clearerr, *II_feof, *II_ferror, *II_fileno;
BuiltinBug *BT_nullfp, *BT_illegalwhence;
BuiltinBug *BT_nullfp, *BT_illegalwhence, *BT_doubleclose;
public:
StreamChecker()
: II_fopen(0), II_fread(0), II_fwrite(0),
: II_fopen(0), II_fclose(0), II_fread(0), II_fwrite(0),
II_fseek(0), II_ftell(0), II_rewind(0), II_fgetpos(0), II_fsetpos(0),
II_clearerr(0), II_feof(0), II_ferror(0), II_fileno(0),
BT_nullfp(0), BT_illegalwhence(0) {}
BT_nullfp(0), BT_illegalwhence(0), BT_doubleclose(0) {}
static void *getTag() {
static int x;
@ -45,6 +69,7 @@ public:
private:
void Fopen(CheckerContext &C, const CallExpr *CE);
void Fclose(CheckerContext &C, const CallExpr *CE);
void Fread(CheckerContext &C, const CallExpr *CE);
void Fwrite(CheckerContext &C, const CallExpr *CE);
void Fseek(CheckerContext &C, const CallExpr *CE);
@ -60,10 +85,20 @@ private:
// Return true indicates the stream pointer is NULL.
const GRState *CheckNullStream(SVal SV, const GRState *state,
CheckerContext &C);
const GRState *CheckDoubleClose(const CallExpr *CE, const GRState *state,
CheckerContext &C);
};
} // end anonymous namespace
namespace clang {
template <>
struct GRStateTrait<StreamState>
: public GRStatePartialTrait<llvm::ImmutableMap<SymbolRef, StreamState> > {
static void *GDMIndex() { return StreamChecker::getTag(); }
};
}
void clang::RegisterStreamChecker(GRExprEngine &Eng) {
Eng.registerCheck(new StreamChecker());
}
@ -79,6 +114,8 @@ bool StreamChecker::EvalCallExpr(CheckerContext &C, const CallExpr *CE) {
ASTContext &Ctx = C.getASTContext();
if (!II_fopen)
II_fopen = &Ctx.Idents.get("fopen");
if (!II_fclose)
II_fclose = &Ctx.Idents.get("fclose");
if (!II_fread)
II_fread = &Ctx.Idents.get("fread");
if (!II_fwrite)
@ -106,6 +143,10 @@ bool StreamChecker::EvalCallExpr(CheckerContext &C, const CallExpr *CE) {
Fopen(C, CE);
return true;
}
if (FD->getIdentifier() == II_fclose) {
Fclose(C, CE);
return true;
}
if (FD->getIdentifier() == II_fread) {
Fread(C, CE);
return true;
@ -168,10 +209,23 @@ void StreamChecker::Fopen(CheckerContext &C, const CallExpr *CE) {
const GRState *stateNotNull, *stateNull;
llvm::tie(stateNotNull, stateNull) = CM.AssumeDual(state, RetVal);
SymbolRef Sym = RetVal.getAsSymbol();
assert(Sym);
// if RetVal is not NULL, set the symbol's state to Opened.
stateNotNull = stateNotNull->set<StreamState>(Sym, StreamState::getOpened(CE));
stateNull = stateNull->set<StreamState>(Sym, StreamState::getOpenFailed(CE));
C.addTransition(stateNotNull);
C.addTransition(stateNull);
}
void StreamChecker::Fclose(CheckerContext &C, const CallExpr *CE) {
const GRState *state = CheckDoubleClose(CE, C.getState(), C);
if (state)
C.addTransition(state);
}
void StreamChecker::Fread(CheckerContext &C, const CallExpr *CE) {
const GRState *state = C.getState();
if (!CheckNullStream(state->getSVal(CE->getArg(3)), state, C))
@ -285,3 +339,32 @@ const GRState *StreamChecker::CheckNullStream(SVal SV, const GRState *state,
}
return stateNotNull;
}
const GRState *StreamChecker::CheckDoubleClose(const CallExpr *CE,
const GRState *state,
CheckerContext &C) {
SymbolRef Sym = state->getSVal(CE->getArg(0)).getAsSymbol();
assert(Sym);
const StreamState *SS = state->get<StreamState>(Sym);
assert(SS);
// Check: Double close a File Descriptor could cause undefined behaviour.
// Conforming to man-pages
if (SS->isClosed()) {
ExplodedNode *N = C.GenerateSink();
if (N) {
if (!BT_doubleclose)
BT_doubleclose = new BuiltinBug("Double fclose",
"Try to close a file Descriptor already"
" closed. Cause undefined behaviour.");
BugReport *R = new BugReport(*BT_doubleclose,
BT_doubleclose->getDescription(), N);
C.EmitReport(R);
}
return NULL;
}
// Close the File Descriptor.
return state->set<StreamState>(Sym, StreamState::getClosed(CE));
}

View File

@ -6,6 +6,7 @@ typedef struct _IO_FILE FILE;
#define SEEK_CUR 1 /* Seek from current position. */
#define SEEK_END 2 /* Seek from end of file. */
extern FILE *fopen(const char *path, const char *mode);
extern int fclose(FILE *fp);
extern size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
extern int fseek (FILE *__stream, long int __off, int __whence);
extern long int ftell (FILE *__stream);
@ -39,3 +40,9 @@ void f5(void) {
fseek(p, 1, SEEK_SET); // no-warning
fseek(p, 1, 3); // expected-warning {{The whence argument to fseek() should be SEEK_SET, SEEK_END, or SEEK_CUR.}}
}
void f6(void) {
FILE *p = fopen("foo", "r");
fclose(p);
fclose(p); // expected-warning {{Try to close a file Descriptor already closed. Cause Undefined Behaviour.}}
}