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/BugReporter/BugType.h" 16 #include "clang/StaticAnalyzer/Core/Checker.h" 17 #include "clang/StaticAnalyzer/Core/CheckerManager.h" 18 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.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 mutable IdentifierInfo *II_fopen, *II_tmpfile, *II_fclose, *II_fread, 62 *II_fwrite, 63 *II_fseek, *II_ftell, *II_rewind, *II_fgetpos, *II_fsetpos, 64 *II_clearerr, *II_feof, *II_ferror, *II_fileno; 65 mutable OwningPtr<BuiltinBug> BT_nullfp, BT_illegalwhence, 66 BT_doubleclose, BT_ResourceLeak; 67 68 public: 69 StreamChecker() 70 : II_fopen(0), II_tmpfile(0) ,II_fclose(0), II_fread(0), II_fwrite(0), 71 II_fseek(0), II_ftell(0), II_rewind(0), II_fgetpos(0), II_fsetpos(0), 72 II_clearerr(0), II_feof(0), II_ferror(0), II_fileno(0) {} 73 74 bool evalCall(const CallExpr *CE, CheckerContext &C) const; 75 void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; 76 77 private: 78 void Fopen(CheckerContext &C, const CallExpr *CE) const; 79 void Tmpfile(CheckerContext &C, const CallExpr *CE) const; 80 void Fclose(CheckerContext &C, const CallExpr *CE) const; 81 void Fread(CheckerContext &C, const CallExpr *CE) const; 82 void Fwrite(CheckerContext &C, const CallExpr *CE) const; 83 void Fseek(CheckerContext &C, const CallExpr *CE) const; 84 void Ftell(CheckerContext &C, const CallExpr *CE) const; 85 void Rewind(CheckerContext &C, const CallExpr *CE) const; 86 void Fgetpos(CheckerContext &C, const CallExpr *CE) const; 87 void Fsetpos(CheckerContext &C, const CallExpr *CE) const; 88 void Clearerr(CheckerContext &C, const CallExpr *CE) const; 89 void Feof(CheckerContext &C, const CallExpr *CE) const; 90 void Ferror(CheckerContext &C, const CallExpr *CE) const; 91 void Fileno(CheckerContext &C, const CallExpr *CE) const; 92 93 void OpenFileAux(CheckerContext &C, const CallExpr *CE) const; 94 95 ProgramStateRef CheckNullStream(SVal SV, ProgramStateRef state, 96 CheckerContext &C) const; 97 ProgramStateRef CheckDoubleClose(const CallExpr *CE, ProgramStateRef state, 98 CheckerContext &C) const; 99 }; 100 101 } // end anonymous namespace 102 103 REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) 104 105 106 bool StreamChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { 107 const FunctionDecl *FD = C.getCalleeDecl(CE); 108 if (!FD || FD->getKind() != Decl::Function) 109 return false; 110 111 ASTContext &Ctx = C.getASTContext(); 112 if (!II_fopen) 113 II_fopen = &Ctx.Idents.get("fopen"); 114 if (!II_tmpfile) 115 II_tmpfile = &Ctx.Idents.get("tmpfile"); 116 if (!II_fclose) 117 II_fclose = &Ctx.Idents.get("fclose"); 118 if (!II_fread) 119 II_fread = &Ctx.Idents.get("fread"); 120 if (!II_fwrite) 121 II_fwrite = &Ctx.Idents.get("fwrite"); 122 if (!II_fseek) 123 II_fseek = &Ctx.Idents.get("fseek"); 124 if (!II_ftell) 125 II_ftell = &Ctx.Idents.get("ftell"); 126 if (!II_rewind) 127 II_rewind = &Ctx.Idents.get("rewind"); 128 if (!II_fgetpos) 129 II_fgetpos = &Ctx.Idents.get("fgetpos"); 130 if (!II_fsetpos) 131 II_fsetpos = &Ctx.Idents.get("fsetpos"); 132 if (!II_clearerr) 133 II_clearerr = &Ctx.Idents.get("clearerr"); 134 if (!II_feof) 135 II_feof = &Ctx.Idents.get("feof"); 136 if (!II_ferror) 137 II_ferror = &Ctx.Idents.get("ferror"); 138 if (!II_fileno) 139 II_fileno = &Ctx.Idents.get("fileno"); 140 141 if (FD->getIdentifier() == II_fopen) { 142 Fopen(C, CE); 143 return true; 144 } 145 if (FD->getIdentifier() == II_tmpfile) { 146 Tmpfile(C, CE); 147 return true; 148 } 149 if (FD->getIdentifier() == II_fclose) { 150 Fclose(C, CE); 151 return true; 152 } 153 if (FD->getIdentifier() == II_fread) { 154 Fread(C, CE); 155 return true; 156 } 157 if (FD->getIdentifier() == II_fwrite) { 158 Fwrite(C, CE); 159 return true; 160 } 161 if (FD->getIdentifier() == II_fseek) { 162 Fseek(C, CE); 163 return true; 164 } 165 if (FD->getIdentifier() == II_ftell) { 166 Ftell(C, CE); 167 return true; 168 } 169 if (FD->getIdentifier() == II_rewind) { 170 Rewind(C, CE); 171 return true; 172 } 173 if (FD->getIdentifier() == II_fgetpos) { 174 Fgetpos(C, CE); 175 return true; 176 } 177 if (FD->getIdentifier() == II_fsetpos) { 178 Fsetpos(C, CE); 179 return true; 180 } 181 if (FD->getIdentifier() == II_clearerr) { 182 Clearerr(C, CE); 183 return true; 184 } 185 if (FD->getIdentifier() == II_feof) { 186 Feof(C, CE); 187 return true; 188 } 189 if (FD->getIdentifier() == II_ferror) { 190 Ferror(C, CE); 191 return true; 192 } 193 if (FD->getIdentifier() == II_fileno) { 194 Fileno(C, CE); 195 return true; 196 } 197 198 return false; 199 } 200 201 void StreamChecker::Fopen(CheckerContext &C, const CallExpr *CE) const { 202 OpenFileAux(C, CE); 203 } 204 205 void StreamChecker::Tmpfile(CheckerContext &C, const CallExpr *CE) const { 206 OpenFileAux(C, CE); 207 } 208 209 void StreamChecker::OpenFileAux(CheckerContext &C, const CallExpr *CE) const { 210 ProgramStateRef state = C.getState(); 211 SValBuilder &svalBuilder = C.getSValBuilder(); 212 const LocationContext *LCtx = C.getPredecessor()->getLocationContext(); 213 DefinedSVal RetVal = svalBuilder.conjureSymbolVal(0, CE, LCtx, C.blockCount()) 214 .castAs<DefinedSVal>(); 215 state = state->BindExpr(CE, C.getLocationContext(), RetVal); 216 217 ConstraintManager &CM = C.getConstraintManager(); 218 // Bifurcate the state into two: one with a valid FILE* pointer, the other 219 // with a NULL. 220 ProgramStateRef stateNotNull, stateNull; 221 llvm::tie(stateNotNull, stateNull) = CM.assumeDual(state, RetVal); 222 223 if (SymbolRef Sym = RetVal.getAsSymbol()) { 224 // if RetVal is not NULL, set the symbol's state to Opened. 225 stateNotNull = 226 stateNotNull->set<StreamMap>(Sym,StreamState::getOpened(CE)); 227 stateNull = 228 stateNull->set<StreamMap>(Sym, StreamState::getOpenFailed(CE)); 229 230 C.addTransition(stateNotNull); 231 C.addTransition(stateNull); 232 } 233 } 234 235 void StreamChecker::Fclose(CheckerContext &C, const CallExpr *CE) const { 236 ProgramStateRef state = CheckDoubleClose(CE, C.getState(), C); 237 if (state) 238 C.addTransition(state); 239 } 240 241 void StreamChecker::Fread(CheckerContext &C, const CallExpr *CE) const { 242 ProgramStateRef state = C.getState(); 243 if (!CheckNullStream(state->getSVal(CE->getArg(3), C.getLocationContext()), 244 state, C)) 245 return; 246 } 247 248 void StreamChecker::Fwrite(CheckerContext &C, const CallExpr *CE) const { 249 ProgramStateRef state = C.getState(); 250 if (!CheckNullStream(state->getSVal(CE->getArg(3), C.getLocationContext()), 251 state, C)) 252 return; 253 } 254 255 void StreamChecker::Fseek(CheckerContext &C, const CallExpr *CE) const { 256 ProgramStateRef state = C.getState(); 257 if (!(state = CheckNullStream(state->getSVal(CE->getArg(0), 258 C.getLocationContext()), state, C))) 259 return; 260 // Check the legality of the 'whence' argument of 'fseek'. 261 SVal Whence = state->getSVal(CE->getArg(2), C.getLocationContext()); 262 Optional<nonloc::ConcreteInt> CI = Whence.getAs<nonloc::ConcreteInt>(); 263 264 if (!CI) 265 return; 266 267 int64_t x = CI->getValue().getSExtValue(); 268 if (x >= 0 && x <= 2) 269 return; 270 271 if (ExplodedNode *N = C.addTransition(state)) { 272 if (!BT_illegalwhence) 273 BT_illegalwhence.reset( 274 new BuiltinBug(this, "Illegal whence argument", 275 "The whence argument to fseek() should be " 276 "SEEK_SET, SEEK_END, or SEEK_CUR.")); 277 BugReport *R = new BugReport(*BT_illegalwhence, 278 BT_illegalwhence->getDescription(), N); 279 C.emitReport(R); 280 } 281 } 282 283 void StreamChecker::Ftell(CheckerContext &C, const CallExpr *CE) const { 284 ProgramStateRef state = C.getState(); 285 if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), 286 state, C)) 287 return; 288 } 289 290 void StreamChecker::Rewind(CheckerContext &C, const CallExpr *CE) const { 291 ProgramStateRef state = C.getState(); 292 if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), 293 state, C)) 294 return; 295 } 296 297 void StreamChecker::Fgetpos(CheckerContext &C, const CallExpr *CE) const { 298 ProgramStateRef state = C.getState(); 299 if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), 300 state, C)) 301 return; 302 } 303 304 void StreamChecker::Fsetpos(CheckerContext &C, const CallExpr *CE) const { 305 ProgramStateRef state = C.getState(); 306 if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), 307 state, C)) 308 return; 309 } 310 311 void StreamChecker::Clearerr(CheckerContext &C, const CallExpr *CE) const { 312 ProgramStateRef state = C.getState(); 313 if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), 314 state, C)) 315 return; 316 } 317 318 void StreamChecker::Feof(CheckerContext &C, const CallExpr *CE) const { 319 ProgramStateRef state = C.getState(); 320 if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), 321 state, C)) 322 return; 323 } 324 325 void StreamChecker::Ferror(CheckerContext &C, const CallExpr *CE) const { 326 ProgramStateRef state = C.getState(); 327 if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), 328 state, C)) 329 return; 330 } 331 332 void StreamChecker::Fileno(CheckerContext &C, const CallExpr *CE) const { 333 ProgramStateRef state = C.getState(); 334 if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), 335 state, C)) 336 return; 337 } 338 339 ProgramStateRef StreamChecker::CheckNullStream(SVal SV, ProgramStateRef state, 340 CheckerContext &C) const { 341 Optional<DefinedSVal> DV = SV.getAs<DefinedSVal>(); 342 if (!DV) 343 return 0; 344 345 ConstraintManager &CM = C.getConstraintManager(); 346 ProgramStateRef stateNotNull, stateNull; 347 llvm::tie(stateNotNull, stateNull) = CM.assumeDual(state, *DV); 348 349 if (!stateNotNull && stateNull) { 350 if (ExplodedNode *N = C.generateSink(stateNull)) { 351 if (!BT_nullfp) 352 BT_nullfp.reset(new BuiltinBug(this, "NULL stream pointer", 353 "Stream pointer might be NULL.")); 354 BugReport *R =new BugReport(*BT_nullfp, BT_nullfp->getDescription(), N); 355 C.emitReport(R); 356 } 357 return 0; 358 } 359 return stateNotNull; 360 } 361 362 ProgramStateRef StreamChecker::CheckDoubleClose(const CallExpr *CE, 363 ProgramStateRef state, 364 CheckerContext &C) const { 365 SymbolRef Sym = 366 state->getSVal(CE->getArg(0), C.getLocationContext()).getAsSymbol(); 367 if (!Sym) 368 return state; 369 370 const StreamState *SS = state->get<StreamMap>(Sym); 371 372 // If the file stream is not tracked, return. 373 if (!SS) 374 return state; 375 376 // Check: Double close a File Descriptor could cause undefined behaviour. 377 // Conforming to man-pages 378 if (SS->isClosed()) { 379 ExplodedNode *N = C.generateSink(); 380 if (N) { 381 if (!BT_doubleclose) 382 BT_doubleclose.reset(new BuiltinBug( 383 this, "Double fclose", "Try to close a file Descriptor already" 384 " closed. Cause undefined behaviour.")); 385 BugReport *R = new BugReport(*BT_doubleclose, 386 BT_doubleclose->getDescription(), N); 387 C.emitReport(R); 388 } 389 return NULL; 390 } 391 392 // Close the File Descriptor. 393 return state->set<StreamMap>(Sym, StreamState::getClosed(CE)); 394 } 395 396 void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, 397 CheckerContext &C) const { 398 // TODO: Clean up the state. 399 for (SymbolReaper::dead_iterator I = SymReaper.dead_begin(), 400 E = SymReaper.dead_end(); I != E; ++I) { 401 SymbolRef Sym = *I; 402 ProgramStateRef state = C.getState(); 403 const StreamState *SS = state->get<StreamMap>(Sym); 404 if (!SS) 405 continue; 406 407 if (SS->isOpened()) { 408 ExplodedNode *N = C.generateSink(); 409 if (N) { 410 if (!BT_ResourceLeak) 411 BT_ResourceLeak.reset(new BuiltinBug( 412 this, "Resource Leak", 413 "Opened File never closed. Potential Resource leak.")); 414 BugReport *R = new BugReport(*BT_ResourceLeak, 415 BT_ResourceLeak->getDescription(), N); 416 C.emitReport(R); 417 } 418 } 419 } 420 } 421 422 void ento::registerStreamChecker(CheckerManager &mgr) { 423 mgr.registerChecker<StreamChecker>(); 424 } 425