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 24 using namespace clang; 25 using namespace ento; 26 27 namespace { 28 29 struct 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 59 class 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 OwningPtr<BuiltinBug> BT_nullfp, BT_illegalwhence, 68 BT_doubleclose, BT_ResourceLeak; 69 70 public: 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 81 private: 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 ProgramStateRef CheckNullStream(SVal SV, ProgramStateRef state, 100 CheckerContext &C) const; 101 ProgramStateRef CheckDoubleClose(const CallExpr *CE, ProgramStateRef state, 102 CheckerContext &C) const; 103 }; 104 105 } // end anonymous namespace 106 107 REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) 108 109 110 bool StreamChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { 111 const FunctionDecl *FD = C.getCalleeDecl(CE); 112 if (!FD || FD->getKind() != Decl::Function) 113 return false; 114 115 ASTContext &Ctx = C.getASTContext(); 116 if (!II_fopen) 117 II_fopen = &Ctx.Idents.get("fopen"); 118 if (!II_tmpfile) 119 II_tmpfile = &Ctx.Idents.get("tmpfile"); 120 if (!II_fclose) 121 II_fclose = &Ctx.Idents.get("fclose"); 122 if (!II_fread) 123 II_fread = &Ctx.Idents.get("fread"); 124 if (!II_fwrite) 125 II_fwrite = &Ctx.Idents.get("fwrite"); 126 if (!II_fseek) 127 II_fseek = &Ctx.Idents.get("fseek"); 128 if (!II_ftell) 129 II_ftell = &Ctx.Idents.get("ftell"); 130 if (!II_rewind) 131 II_rewind = &Ctx.Idents.get("rewind"); 132 if (!II_fgetpos) 133 II_fgetpos = &Ctx.Idents.get("fgetpos"); 134 if (!II_fsetpos) 135 II_fsetpos = &Ctx.Idents.get("fsetpos"); 136 if (!II_clearerr) 137 II_clearerr = &Ctx.Idents.get("clearerr"); 138 if (!II_feof) 139 II_feof = &Ctx.Idents.get("feof"); 140 if (!II_ferror) 141 II_ferror = &Ctx.Idents.get("ferror"); 142 if (!II_fileno) 143 II_fileno = &Ctx.Idents.get("fileno"); 144 145 if (FD->getIdentifier() == II_fopen) { 146 Fopen(C, CE); 147 return true; 148 } 149 if (FD->getIdentifier() == II_tmpfile) { 150 Tmpfile(C, CE); 151 return true; 152 } 153 if (FD->getIdentifier() == II_fclose) { 154 Fclose(C, CE); 155 return true; 156 } 157 if (FD->getIdentifier() == II_fread) { 158 Fread(C, CE); 159 return true; 160 } 161 if (FD->getIdentifier() == II_fwrite) { 162 Fwrite(C, CE); 163 return true; 164 } 165 if (FD->getIdentifier() == II_fseek) { 166 Fseek(C, CE); 167 return true; 168 } 169 if (FD->getIdentifier() == II_ftell) { 170 Ftell(C, CE); 171 return true; 172 } 173 if (FD->getIdentifier() == II_rewind) { 174 Rewind(C, CE); 175 return true; 176 } 177 if (FD->getIdentifier() == II_fgetpos) { 178 Fgetpos(C, CE); 179 return true; 180 } 181 if (FD->getIdentifier() == II_fsetpos) { 182 Fsetpos(C, CE); 183 return true; 184 } 185 if (FD->getIdentifier() == II_clearerr) { 186 Clearerr(C, CE); 187 return true; 188 } 189 if (FD->getIdentifier() == II_feof) { 190 Feof(C, CE); 191 return true; 192 } 193 if (FD->getIdentifier() == II_ferror) { 194 Ferror(C, CE); 195 return true; 196 } 197 if (FD->getIdentifier() == II_fileno) { 198 Fileno(C, CE); 199 return true; 200 } 201 202 return false; 203 } 204 205 void StreamChecker::Fopen(CheckerContext &C, const CallExpr *CE) const { 206 OpenFileAux(C, CE); 207 } 208 209 void StreamChecker::Tmpfile(CheckerContext &C, const CallExpr *CE) const { 210 OpenFileAux(C, CE); 211 } 212 213 void StreamChecker::OpenFileAux(CheckerContext &C, const CallExpr *CE) const { 214 ProgramStateRef state = C.getState(); 215 SValBuilder &svalBuilder = C.getSValBuilder(); 216 const LocationContext *LCtx = C.getPredecessor()->getLocationContext(); 217 DefinedSVal RetVal = 218 cast<DefinedSVal>(svalBuilder.conjureSymbolVal(0, CE, LCtx, 219 C.blockCount())); 220 state = state->BindExpr(CE, C.getLocationContext(), RetVal); 221 222 ConstraintManager &CM = C.getConstraintManager(); 223 // Bifurcate the state into two: one with a valid FILE* pointer, the other 224 // with a NULL. 225 ProgramStateRef stateNotNull, stateNull; 226 llvm::tie(stateNotNull, stateNull) = CM.assumeDual(state, RetVal); 227 228 if (SymbolRef Sym = RetVal.getAsSymbol()) { 229 // if RetVal is not NULL, set the symbol's state to Opened. 230 stateNotNull = 231 stateNotNull->set<StreamMap>(Sym,StreamState::getOpened(CE)); 232 stateNull = 233 stateNull->set<StreamMap>(Sym, StreamState::getOpenFailed(CE)); 234 235 C.addTransition(stateNotNull); 236 C.addTransition(stateNull); 237 } 238 } 239 240 void StreamChecker::Fclose(CheckerContext &C, const CallExpr *CE) const { 241 ProgramStateRef state = CheckDoubleClose(CE, C.getState(), C); 242 if (state) 243 C.addTransition(state); 244 } 245 246 void StreamChecker::Fread(CheckerContext &C, const CallExpr *CE) const { 247 ProgramStateRef state = C.getState(); 248 if (!CheckNullStream(state->getSVal(CE->getArg(3), C.getLocationContext()), 249 state, C)) 250 return; 251 } 252 253 void StreamChecker::Fwrite(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 260 void StreamChecker::Fseek(CheckerContext &C, const CallExpr *CE) const { 261 ProgramStateRef state = C.getState(); 262 if (!(state = CheckNullStream(state->getSVal(CE->getArg(0), 263 C.getLocationContext()), state, C))) 264 return; 265 // Check the legality of the 'whence' argument of 'fseek'. 266 SVal Whence = state->getSVal(CE->getArg(2), C.getLocationContext()); 267 const nonloc::ConcreteInt *CI = dyn_cast<nonloc::ConcreteInt>(&Whence); 268 269 if (!CI) 270 return; 271 272 int64_t x = CI->getValue().getSExtValue(); 273 if (x >= 0 && x <= 2) 274 return; 275 276 if (ExplodedNode *N = C.addTransition(state)) { 277 if (!BT_illegalwhence) 278 BT_illegalwhence.reset(new BuiltinBug("Illegal whence argument", 279 "The whence argument to fseek() should be " 280 "SEEK_SET, SEEK_END, or SEEK_CUR.")); 281 BugReport *R = new BugReport(*BT_illegalwhence, 282 BT_illegalwhence->getDescription(), N); 283 C.emitReport(R); 284 } 285 } 286 287 void StreamChecker::Ftell(CheckerContext &C, const CallExpr *CE) const { 288 ProgramStateRef state = C.getState(); 289 if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), 290 state, C)) 291 return; 292 } 293 294 void StreamChecker::Rewind(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 301 void StreamChecker::Fgetpos(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 308 void StreamChecker::Fsetpos(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 315 void StreamChecker::Clearerr(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 322 void StreamChecker::Feof(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 329 void StreamChecker::Ferror(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 336 void StreamChecker::Fileno(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 343 ProgramStateRef StreamChecker::CheckNullStream(SVal SV, ProgramStateRef state, 344 CheckerContext &C) const { 345 const DefinedSVal *DV = dyn_cast<DefinedSVal>(&SV); 346 if (!DV) 347 return 0; 348 349 ConstraintManager &CM = C.getConstraintManager(); 350 ProgramStateRef stateNotNull, stateNull; 351 llvm::tie(stateNotNull, stateNull) = CM.assumeDual(state, *DV); 352 353 if (!stateNotNull && stateNull) { 354 if (ExplodedNode *N = C.generateSink(stateNull)) { 355 if (!BT_nullfp) 356 BT_nullfp.reset(new BuiltinBug("NULL stream pointer", 357 "Stream pointer might be NULL.")); 358 BugReport *R =new BugReport(*BT_nullfp, BT_nullfp->getDescription(), N); 359 C.emitReport(R); 360 } 361 return 0; 362 } 363 return stateNotNull; 364 } 365 366 ProgramStateRef StreamChecker::CheckDoubleClose(const CallExpr *CE, 367 ProgramStateRef state, 368 CheckerContext &C) const { 369 SymbolRef Sym = 370 state->getSVal(CE->getArg(0), C.getLocationContext()).getAsSymbol(); 371 if (!Sym) 372 return state; 373 374 const StreamState *SS = state->get<StreamMap>(Sym); 375 376 // If the file stream is not tracked, return. 377 if (!SS) 378 return state; 379 380 // Check: Double close a File Descriptor could cause undefined behaviour. 381 // Conforming to man-pages 382 if (SS->isClosed()) { 383 ExplodedNode *N = C.generateSink(); 384 if (N) { 385 if (!BT_doubleclose) 386 BT_doubleclose.reset(new BuiltinBug("Double fclose", 387 "Try to close a file Descriptor already" 388 " closed. Cause undefined behaviour.")); 389 BugReport *R = new BugReport(*BT_doubleclose, 390 BT_doubleclose->getDescription(), N); 391 C.emitReport(R); 392 } 393 return NULL; 394 } 395 396 // Close the File Descriptor. 397 return state->set<StreamMap>(Sym, StreamState::getClosed(CE)); 398 } 399 400 void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, 401 CheckerContext &C) const { 402 // TODO: Clean up the state. 403 for (SymbolReaper::dead_iterator I = SymReaper.dead_begin(), 404 E = SymReaper.dead_end(); I != E; ++I) { 405 SymbolRef Sym = *I; 406 ProgramStateRef state = C.getState(); 407 const StreamState *SS = state->get<StreamMap>(Sym); 408 // TODO: Shouldn't we have a continue here? 409 if (!SS) 410 return; 411 412 if (SS->isOpened()) { 413 ExplodedNode *N = C.generateSink(); 414 if (N) { 415 if (!BT_ResourceLeak) 416 BT_ResourceLeak.reset(new BuiltinBug("Resource Leak", 417 "Opened File never closed. Potential Resource leak.")); 418 BugReport *R = new BugReport(*BT_ResourceLeak, 419 BT_ResourceLeak->getDescription(), N); 420 C.emitReport(R); 421 } 422 } 423 } 424 } 425 426 void StreamChecker::checkEndPath(CheckerContext &Ctx) const { 427 ProgramStateRef state = Ctx.getState(); 428 StreamMapTy M = state->get<StreamMap>(); 429 430 for (StreamMapTy::iterator I = M.begin(), E = M.end(); I != E; ++I) { 431 StreamState SS = I->second; 432 if (SS.isOpened()) { 433 ExplodedNode *N = Ctx.addTransition(state); 434 if (N) { 435 if (!BT_ResourceLeak) 436 BT_ResourceLeak.reset(new BuiltinBug("Resource Leak", 437 "Opened File never closed. Potential Resource leak.")); 438 BugReport *R = new BugReport(*BT_ResourceLeak, 439 BT_ResourceLeak->getDescription(), N); 440 Ctx.emitReport(R); 441 } 442 } 443 } 444 } 445 446 void StreamChecker::checkPreStmt(const ReturnStmt *S, CheckerContext &C) const { 447 const Expr *RetE = S->getRetValue(); 448 if (!RetE) 449 return; 450 451 ProgramStateRef state = C.getState(); 452 SymbolRef Sym = state->getSVal(RetE, C.getLocationContext()).getAsSymbol(); 453 454 if (!Sym) 455 return; 456 457 const StreamState *SS = state->get<StreamMap>(Sym); 458 if(!SS) 459 return; 460 461 if (SS->isOpened()) 462 state = state->set<StreamMap>(Sym, StreamState::getEscaped(S)); 463 464 C.addTransition(state); 465 } 466 467 void ento::registerStreamChecker(CheckerManager &mgr) { 468 mgr.registerChecker<StreamChecker>(); 469 } 470