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