StreamChecker.cpp revision 5eca482fe895ea57bc82410222e6426c09e63284
1//===-- StreamChecker.cpp -----------------------------------------*- C++ -*--// 2// 3// The LLVM Compiler Infrastructure 4// 5// This file is distributed under the University of Illinois Open Source 6// License. See LICENSE.TXT for details. 7// 8//===----------------------------------------------------------------------===// 9// 10// This file defines checkers that model and check stream handling functions. 11// 12//===----------------------------------------------------------------------===// 13 14#include "ClangSACheckers.h" 15#include "clang/StaticAnalyzer/Core/Checker.h" 16#include "clang/StaticAnalyzer/Core/CheckerManager.h" 17#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 18#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 19#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" 20#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" 21#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" 22#include "llvm/ADT/ImmutableMap.h" 23 24using namespace clang; 25using namespace ento; 26 27namespace { 28 29struct StreamState { 30 enum Kind { Opened, Closed, OpenFailed, Escaped } K; 31 const Stmt *S; 32 33 StreamState(Kind k, const Stmt *s) : K(k), S(s) {} 34 35 bool isOpened() const { return K == Opened; } 36 bool isClosed() const { return K == Closed; } 37 //bool isOpenFailed() const { return K == OpenFailed; } 38 //bool isEscaped() const { return K == Escaped; } 39 40 bool operator==(const StreamState &X) const { 41 return K == X.K && S == X.S; 42 } 43 44 static StreamState getOpened(const Stmt *s) { return StreamState(Opened, s); } 45 static StreamState getClosed(const Stmt *s) { return StreamState(Closed, s); } 46 static StreamState getOpenFailed(const Stmt *s) { 47 return StreamState(OpenFailed, s); 48 } 49 static StreamState getEscaped(const Stmt *s) { 50 return StreamState(Escaped, s); 51 } 52 53 void Profile(llvm::FoldingSetNodeID &ID) const { 54 ID.AddInteger(K); 55 ID.AddPointer(S); 56 } 57}; 58 59class StreamChecker : public Checker<eval::Call, 60 check::DeadSymbols, 61 check::EndPath, 62 check::PreStmt<ReturnStmt> > { 63 mutable IdentifierInfo *II_fopen, *II_tmpfile, *II_fclose, *II_fread, 64 *II_fwrite, 65 *II_fseek, *II_ftell, *II_rewind, *II_fgetpos, *II_fsetpos, 66 *II_clearerr, *II_feof, *II_ferror, *II_fileno; 67 mutable llvm::OwningPtr<BuiltinBug> BT_nullfp, BT_illegalwhence, 68 BT_doubleclose, BT_ResourceLeak; 69 70public: 71 StreamChecker() 72 : II_fopen(0), II_tmpfile(0) ,II_fclose(0), II_fread(0), II_fwrite(0), 73 II_fseek(0), II_ftell(0), II_rewind(0), II_fgetpos(0), II_fsetpos(0), 74 II_clearerr(0), II_feof(0), II_ferror(0), II_fileno(0) {} 75 76 bool evalCall(const CallExpr *CE, CheckerContext &C) const; 77 void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; 78 void checkEndPath(CheckerContext &Ctx) const; 79 void checkPreStmt(const ReturnStmt *S, CheckerContext &C) const; 80 81private: 82 void Fopen(CheckerContext &C, const CallExpr *CE) const; 83 void Tmpfile(CheckerContext &C, const CallExpr *CE) const; 84 void Fclose(CheckerContext &C, const CallExpr *CE) const; 85 void Fread(CheckerContext &C, const CallExpr *CE) const; 86 void Fwrite(CheckerContext &C, const CallExpr *CE) const; 87 void Fseek(CheckerContext &C, const CallExpr *CE) const; 88 void Ftell(CheckerContext &C, const CallExpr *CE) const; 89 void Rewind(CheckerContext &C, const CallExpr *CE) const; 90 void Fgetpos(CheckerContext &C, const CallExpr *CE) const; 91 void Fsetpos(CheckerContext &C, const CallExpr *CE) const; 92 void Clearerr(CheckerContext &C, const CallExpr *CE) const; 93 void Feof(CheckerContext &C, const CallExpr *CE) const; 94 void Ferror(CheckerContext &C, const CallExpr *CE) const; 95 void Fileno(CheckerContext &C, const CallExpr *CE) const; 96 97 void OpenFileAux(CheckerContext &C, const CallExpr *CE) const; 98 99 const ProgramState *CheckNullStream(SVal SV, const ProgramState *state, 100 CheckerContext &C) const; 101 const ProgramState *CheckDoubleClose(const CallExpr *CE, const ProgramState *state, 102 CheckerContext &C) const; 103}; 104 105} // end anonymous namespace 106 107namespace clang { 108namespace ento { 109 template <> 110 struct ProgramStateTrait<StreamState> 111 : public ProgramStatePartialTrait<llvm::ImmutableMap<SymbolRef, StreamState> > { 112 static void *GDMIndex() { static int x; return &x; } 113 }; 114} 115} 116 117bool StreamChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { 118 const FunctionDecl *FD = C.getCalleeDecl(CE); 119 if (!FD) 120 return false; 121 122 ASTContext &Ctx = C.getASTContext(); 123 if (!II_fopen) 124 II_fopen = &Ctx.Idents.get("fopen"); 125 if (!II_tmpfile) 126 II_tmpfile = &Ctx.Idents.get("tmpfile"); 127 if (!II_fclose) 128 II_fclose = &Ctx.Idents.get("fclose"); 129 if (!II_fread) 130 II_fread = &Ctx.Idents.get("fread"); 131 if (!II_fwrite) 132 II_fwrite = &Ctx.Idents.get("fwrite"); 133 if (!II_fseek) 134 II_fseek = &Ctx.Idents.get("fseek"); 135 if (!II_ftell) 136 II_ftell = &Ctx.Idents.get("ftell"); 137 if (!II_rewind) 138 II_rewind = &Ctx.Idents.get("rewind"); 139 if (!II_fgetpos) 140 II_fgetpos = &Ctx.Idents.get("fgetpos"); 141 if (!II_fsetpos) 142 II_fsetpos = &Ctx.Idents.get("fsetpos"); 143 if (!II_clearerr) 144 II_clearerr = &Ctx.Idents.get("clearerr"); 145 if (!II_feof) 146 II_feof = &Ctx.Idents.get("feof"); 147 if (!II_ferror) 148 II_ferror = &Ctx.Idents.get("ferror"); 149 if (!II_fileno) 150 II_fileno = &Ctx.Idents.get("fileno"); 151 152 if (FD->getIdentifier() == II_fopen) { 153 Fopen(C, CE); 154 return true; 155 } 156 if (FD->getIdentifier() == II_tmpfile) { 157 Tmpfile(C, CE); 158 return true; 159 } 160 if (FD->getIdentifier() == II_fclose) { 161 Fclose(C, CE); 162 return true; 163 } 164 if (FD->getIdentifier() == II_fread) { 165 Fread(C, CE); 166 return true; 167 } 168 if (FD->getIdentifier() == II_fwrite) { 169 Fwrite(C, CE); 170 return true; 171 } 172 if (FD->getIdentifier() == II_fseek) { 173 Fseek(C, CE); 174 return true; 175 } 176 if (FD->getIdentifier() == II_ftell) { 177 Ftell(C, CE); 178 return true; 179 } 180 if (FD->getIdentifier() == II_rewind) { 181 Rewind(C, CE); 182 return true; 183 } 184 if (FD->getIdentifier() == II_fgetpos) { 185 Fgetpos(C, CE); 186 return true; 187 } 188 if (FD->getIdentifier() == II_fsetpos) { 189 Fsetpos(C, CE); 190 return true; 191 } 192 if (FD->getIdentifier() == II_clearerr) { 193 Clearerr(C, CE); 194 return true; 195 } 196 if (FD->getIdentifier() == II_feof) { 197 Feof(C, CE); 198 return true; 199 } 200 if (FD->getIdentifier() == II_ferror) { 201 Ferror(C, CE); 202 return true; 203 } 204 if (FD->getIdentifier() == II_fileno) { 205 Fileno(C, CE); 206 return true; 207 } 208 209 return false; 210} 211 212void StreamChecker::Fopen(CheckerContext &C, const CallExpr *CE) const { 213 OpenFileAux(C, CE); 214} 215 216void StreamChecker::Tmpfile(CheckerContext &C, const CallExpr *CE) const { 217 OpenFileAux(C, CE); 218} 219 220void StreamChecker::OpenFileAux(CheckerContext &C, const CallExpr *CE) const { 221 const ProgramState *state = C.getState(); 222 unsigned Count = C.getCurrentBlockCount(); 223 SValBuilder &svalBuilder = C.getSValBuilder(); 224 DefinedSVal RetVal = 225 cast<DefinedSVal>(svalBuilder.getConjuredSymbolVal(0, CE, Count)); 226 state = state->BindExpr(CE, C.getLocationContext(), RetVal); 227 228 ConstraintManager &CM = C.getConstraintManager(); 229 // Bifurcate the state into two: one with a valid FILE* pointer, the other 230 // with a NULL. 231 const ProgramState *stateNotNull, *stateNull; 232 llvm::tie(stateNotNull, stateNull) = CM.assumeDual(state, RetVal); 233 234 if (SymbolRef Sym = RetVal.getAsSymbol()) { 235 // if RetVal is not NULL, set the symbol's state to Opened. 236 stateNotNull = 237 stateNotNull->set<StreamState>(Sym,StreamState::getOpened(CE)); 238 stateNull = 239 stateNull->set<StreamState>(Sym, StreamState::getOpenFailed(CE)); 240 241 C.addTransition(stateNotNull); 242 C.addTransition(stateNull); 243 } 244} 245 246void StreamChecker::Fclose(CheckerContext &C, const CallExpr *CE) const { 247 const ProgramState *state = CheckDoubleClose(CE, C.getState(), C); 248 if (state) 249 C.addTransition(state); 250} 251 252void StreamChecker::Fread(CheckerContext &C, const CallExpr *CE) const { 253 const ProgramState *state = C.getState(); 254 if (!CheckNullStream(state->getSVal(CE->getArg(3), C.getLocationContext()), 255 state, C)) 256 return; 257} 258 259void StreamChecker::Fwrite(CheckerContext &C, const CallExpr *CE) const { 260 const ProgramState *state = C.getState(); 261 if (!CheckNullStream(state->getSVal(CE->getArg(3), C.getLocationContext()), 262 state, C)) 263 return; 264} 265 266void StreamChecker::Fseek(CheckerContext &C, const CallExpr *CE) const { 267 const ProgramState *state = C.getState(); 268 if (!(state = CheckNullStream(state->getSVal(CE->getArg(0), 269 C.getLocationContext()), state, C))) 270 return; 271 // Check the legality of the 'whence' argument of 'fseek'. 272 SVal Whence = state->getSVal(CE->getArg(2), C.getLocationContext()); 273 const nonloc::ConcreteInt *CI = dyn_cast<nonloc::ConcreteInt>(&Whence); 274 275 if (!CI) 276 return; 277 278 int64_t x = CI->getValue().getSExtValue(); 279 if (x >= 0 && x <= 2) 280 return; 281 282 if (ExplodedNode *N = C.addTransition(state)) { 283 if (!BT_illegalwhence) 284 BT_illegalwhence.reset(new BuiltinBug("Illegal whence argument", 285 "The whence argument to fseek() should be " 286 "SEEK_SET, SEEK_END, or SEEK_CUR.")); 287 BugReport *R = new BugReport(*BT_illegalwhence, 288 BT_illegalwhence->getDescription(), N); 289 C.EmitReport(R); 290 } 291} 292 293void StreamChecker::Ftell(CheckerContext &C, const CallExpr *CE) const { 294 const ProgramState *state = C.getState(); 295 if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), 296 state, C)) 297 return; 298} 299 300void StreamChecker::Rewind(CheckerContext &C, const CallExpr *CE) const { 301 const ProgramState *state = C.getState(); 302 if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), 303 state, C)) 304 return; 305} 306 307void StreamChecker::Fgetpos(CheckerContext &C, const CallExpr *CE) const { 308 const ProgramState *state = C.getState(); 309 if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), 310 state, C)) 311 return; 312} 313 314void StreamChecker::Fsetpos(CheckerContext &C, const CallExpr *CE) const { 315 const ProgramState *state = C.getState(); 316 if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), 317 state, C)) 318 return; 319} 320 321void StreamChecker::Clearerr(CheckerContext &C, const CallExpr *CE) const { 322 const ProgramState *state = C.getState(); 323 if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), 324 state, C)) 325 return; 326} 327 328void StreamChecker::Feof(CheckerContext &C, const CallExpr *CE) const { 329 const ProgramState *state = C.getState(); 330 if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), 331 state, C)) 332 return; 333} 334 335void StreamChecker::Ferror(CheckerContext &C, const CallExpr *CE) const { 336 const ProgramState *state = C.getState(); 337 if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), 338 state, C)) 339 return; 340} 341 342void StreamChecker::Fileno(CheckerContext &C, const CallExpr *CE) const { 343 const ProgramState *state = C.getState(); 344 if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), 345 state, C)) 346 return; 347} 348 349const ProgramState *StreamChecker::CheckNullStream(SVal SV, const ProgramState *state, 350 CheckerContext &C) const { 351 const DefinedSVal *DV = dyn_cast<DefinedSVal>(&SV); 352 if (!DV) 353 return 0; 354 355 ConstraintManager &CM = C.getConstraintManager(); 356 const ProgramState *stateNotNull, *stateNull; 357 llvm::tie(stateNotNull, stateNull) = CM.assumeDual(state, *DV); 358 359 if (!stateNotNull && stateNull) { 360 if (ExplodedNode *N = C.generateSink(stateNull)) { 361 if (!BT_nullfp) 362 BT_nullfp.reset(new BuiltinBug("NULL stream pointer", 363 "Stream pointer might be NULL.")); 364 BugReport *R =new BugReport(*BT_nullfp, BT_nullfp->getDescription(), N); 365 C.EmitReport(R); 366 } 367 return 0; 368 } 369 return stateNotNull; 370} 371 372const ProgramState *StreamChecker::CheckDoubleClose(const CallExpr *CE, 373 const ProgramState *state, 374 CheckerContext &C) const { 375 SymbolRef Sym = 376 state->getSVal(CE->getArg(0), C.getLocationContext()).getAsSymbol(); 377 if (!Sym) 378 return state; 379 380 const StreamState *SS = state->get<StreamState>(Sym); 381 382 // If the file stream is not tracked, return. 383 if (!SS) 384 return state; 385 386 // Check: Double close a File Descriptor could cause undefined behaviour. 387 // Conforming to man-pages 388 if (SS->isClosed()) { 389 ExplodedNode *N = C.generateSink(); 390 if (N) { 391 if (!BT_doubleclose) 392 BT_doubleclose.reset(new BuiltinBug("Double fclose", 393 "Try to close a file Descriptor already" 394 " closed. Cause undefined behaviour.")); 395 BugReport *R = new BugReport(*BT_doubleclose, 396 BT_doubleclose->getDescription(), N); 397 C.EmitReport(R); 398 } 399 return NULL; 400 } 401 402 // Close the File Descriptor. 403 return state->set<StreamState>(Sym, StreamState::getClosed(CE)); 404} 405 406void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, 407 CheckerContext &C) const { 408 for (SymbolReaper::dead_iterator I = SymReaper.dead_begin(), 409 E = SymReaper.dead_end(); I != E; ++I) { 410 SymbolRef Sym = *I; 411 const ProgramState *state = C.getState(); 412 const StreamState *SS = state->get<StreamState>(Sym); 413 if (!SS) 414 return; 415 416 if (SS->isOpened()) { 417 ExplodedNode *N = C.generateSink(); 418 if (N) { 419 if (!BT_ResourceLeak) 420 BT_ResourceLeak.reset(new BuiltinBug("Resource Leak", 421 "Opened File never closed. Potential Resource leak.")); 422 BugReport *R = new BugReport(*BT_ResourceLeak, 423 BT_ResourceLeak->getDescription(), N); 424 C.EmitReport(R); 425 } 426 } 427 } 428} 429 430void StreamChecker::checkEndPath(CheckerContext &Ctx) const { 431 const ProgramState *state = Ctx.getState(); 432 typedef llvm::ImmutableMap<SymbolRef, StreamState> SymMap; 433 SymMap M = state->get<StreamState>(); 434 435 for (SymMap::iterator I = M.begin(), E = M.end(); I != E; ++I) { 436 StreamState SS = I->second; 437 if (SS.isOpened()) { 438 ExplodedNode *N = Ctx.addTransition(state); 439 if (N) { 440 if (!BT_ResourceLeak) 441 BT_ResourceLeak.reset(new BuiltinBug("Resource Leak", 442 "Opened File never closed. Potential Resource leak.")); 443 BugReport *R = new BugReport(*BT_ResourceLeak, 444 BT_ResourceLeak->getDescription(), N); 445 Ctx.EmitReport(R); 446 } 447 } 448 } 449} 450 451void StreamChecker::checkPreStmt(const ReturnStmt *S, CheckerContext &C) const { 452 const Expr *RetE = S->getRetValue(); 453 if (!RetE) 454 return; 455 456 const ProgramState *state = C.getState(); 457 SymbolRef Sym = state->getSVal(RetE, C.getLocationContext()).getAsSymbol(); 458 459 if (!Sym) 460 return; 461 462 const StreamState *SS = state->get<StreamState>(Sym); 463 if(!SS) 464 return; 465 466 if (SS->isOpened()) 467 state = state->set<StreamState>(Sym, StreamState::getEscaped(S)); 468 469 C.addTransition(state); 470} 471 472void ento::registerStreamChecker(CheckerManager &mgr) { 473 mgr.registerChecker<StreamChecker>(); 474} 475