1 //===-- SimpleStreamChecker.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 // Defines a checker for proper use of fopen/fclose APIs. 11 // - If a file has been closed with fclose, it should not be accessed again. 12 // Accessing a closed file results in undefined behavior. 13 // - If a file was opened with fopen, it must be closed with fclose before 14 // the execution ends. Failing to do so results in a resource leak. 15 // 16 //===----------------------------------------------------------------------===// 17 18 #include "ClangSACheckers.h" 19 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 20 #include "clang/StaticAnalyzer/Core/Checker.h" 21 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" 22 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 23 24 using namespace clang; 25 using namespace ento; 26 27 namespace { 28 typedef SmallVector<SymbolRef, 2> SymbolVector; 29 30 struct StreamState { 31 private: 32 enum Kind { Opened, Closed } K; 33 StreamState(Kind InK) : K(InK) { } 34 35 public: 36 bool isOpened() const { return K == Opened; } 37 bool isClosed() const { return K == Closed; } 38 39 static StreamState getOpened() { return StreamState(Opened); } 40 static StreamState getClosed() { return StreamState(Closed); } 41 42 bool operator==(const StreamState &X) const { 43 return K == X.K; 44 } 45 void Profile(llvm::FoldingSetNodeID &ID) const { 46 ID.AddInteger(K); 47 } 48 }; 49 50 class SimpleStreamChecker : public Checker<check::PostCall, 51 check::PreCall, 52 check::DeadSymbols, 53 check::PointerEscape> { 54 55 mutable IdentifierInfo *IIfopen, *IIfclose; 56 57 std::unique_ptr<BugType> DoubleCloseBugType; 58 std::unique_ptr<BugType> LeakBugType; 59 60 void initIdentifierInfo(ASTContext &Ctx) const; 61 62 void reportDoubleClose(SymbolRef FileDescSym, 63 const CallEvent &Call, 64 CheckerContext &C) const; 65 66 void reportLeaks(ArrayRef<SymbolRef> LeakedStreams, CheckerContext &C, 67 ExplodedNode *ErrNode) const; 68 69 bool guaranteedNotToCloseFile(const CallEvent &Call) const; 70 71 public: 72 SimpleStreamChecker(); 73 74 /// Process fopen. 75 void checkPostCall(const CallEvent &Call, CheckerContext &C) const; 76 /// Process fclose. 77 void checkPreCall(const CallEvent &Call, CheckerContext &C) const; 78 79 void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; 80 81 /// Stop tracking addresses which escape. 82 ProgramStateRef checkPointerEscape(ProgramStateRef State, 83 const InvalidatedSymbols &Escaped, 84 const CallEvent *Call, 85 PointerEscapeKind Kind) const; 86 }; 87 88 } // end anonymous namespace 89 90 /// The state of the checker is a map from tracked stream symbols to their 91 /// state. Let's store it in the ProgramState. 92 REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) 93 94 namespace { 95 class StopTrackingCallback final : public SymbolVisitor { 96 ProgramStateRef state; 97 public: 98 StopTrackingCallback(ProgramStateRef st) : state(st) {} 99 ProgramStateRef getState() const { return state; } 100 101 bool VisitSymbol(SymbolRef sym) override { 102 state = state->remove<StreamMap>(sym); 103 return true; 104 } 105 }; 106 } // end anonymous namespace 107 108 SimpleStreamChecker::SimpleStreamChecker() 109 : IIfopen(nullptr), IIfclose(nullptr) { 110 // Initialize the bug types. 111 DoubleCloseBugType.reset( 112 new BugType(this, "Double fclose", "Unix Stream API Error")); 113 114 LeakBugType.reset( 115 new BugType(this, "Resource Leak", "Unix Stream API Error")); 116 // Sinks are higher importance bugs as well as calls to assert() or exit(0). 117 LeakBugType->setSuppressOnSink(true); 118 } 119 120 void SimpleStreamChecker::checkPostCall(const CallEvent &Call, 121 CheckerContext &C) const { 122 initIdentifierInfo(C.getASTContext()); 123 124 if (!Call.isGlobalCFunction()) 125 return; 126 127 if (Call.getCalleeIdentifier() != IIfopen) 128 return; 129 130 // Get the symbolic value corresponding to the file handle. 131 SymbolRef FileDesc = Call.getReturnValue().getAsSymbol(); 132 if (!FileDesc) 133 return; 134 135 // Generate the next transition (an edge in the exploded graph). 136 ProgramStateRef State = C.getState(); 137 State = State->set<StreamMap>(FileDesc, StreamState::getOpened()); 138 C.addTransition(State); 139 } 140 141 void SimpleStreamChecker::checkPreCall(const CallEvent &Call, 142 CheckerContext &C) const { 143 initIdentifierInfo(C.getASTContext()); 144 145 if (!Call.isGlobalCFunction()) 146 return; 147 148 if (Call.getCalleeIdentifier() != IIfclose) 149 return; 150 151 if (Call.getNumArgs() != 1) 152 return; 153 154 // Get the symbolic value corresponding to the file handle. 155 SymbolRef FileDesc = Call.getArgSVal(0).getAsSymbol(); 156 if (!FileDesc) 157 return; 158 159 // Check if the stream has already been closed. 160 ProgramStateRef State = C.getState(); 161 const StreamState *SS = State->get<StreamMap>(FileDesc); 162 if (SS && SS->isClosed()) { 163 reportDoubleClose(FileDesc, Call, C); 164 return; 165 } 166 167 // Generate the next transition, in which the stream is closed. 168 State = State->set<StreamMap>(FileDesc, StreamState::getClosed()); 169 C.addTransition(State); 170 } 171 172 static bool isLeaked(SymbolRef Sym, const StreamState &SS, 173 bool IsSymDead, ProgramStateRef State) { 174 if (IsSymDead && SS.isOpened()) { 175 // If a symbol is NULL, assume that fopen failed on this path. 176 // A symbol should only be considered leaked if it is non-null. 177 ConstraintManager &CMgr = State->getConstraintManager(); 178 ConditionTruthVal OpenFailed = CMgr.isNull(State, Sym); 179 return !OpenFailed.isConstrainedTrue(); 180 } 181 return false; 182 } 183 184 void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, 185 CheckerContext &C) const { 186 ProgramStateRef State = C.getState(); 187 SymbolVector LeakedStreams; 188 StreamMapTy TrackedStreams = State->get<StreamMap>(); 189 for (StreamMapTy::iterator I = TrackedStreams.begin(), 190 E = TrackedStreams.end(); I != E; ++I) { 191 SymbolRef Sym = I->first; 192 bool IsSymDead = SymReaper.isDead(Sym); 193 194 // Collect leaked symbols. 195 if (isLeaked(Sym, I->second, IsSymDead, State)) 196 LeakedStreams.push_back(Sym); 197 198 // Remove the dead symbol from the streams map. 199 if (IsSymDead) 200 State = State->remove<StreamMap>(Sym); 201 } 202 203 ExplodedNode *N = C.generateNonFatalErrorNode(State); 204 if (!N) 205 return; 206 reportLeaks(LeakedStreams, C, N); 207 } 208 209 void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym, 210 const CallEvent &Call, 211 CheckerContext &C) const { 212 // We reached a bug, stop exploring the path here by generating a sink. 213 ExplodedNode *ErrNode = C.generateErrorNode(); 214 // If we've already reached this node on another path, return. 215 if (!ErrNode) 216 return; 217 218 // Generate the report. 219 auto R = llvm::make_unique<BugReport>(*DoubleCloseBugType, 220 "Closing a previously closed file stream", ErrNode); 221 R->addRange(Call.getSourceRange()); 222 R->markInteresting(FileDescSym); 223 C.emitReport(std::move(R)); 224 } 225 226 void SimpleStreamChecker::reportLeaks(ArrayRef<SymbolRef> LeakedStreams, 227 CheckerContext &C, 228 ExplodedNode *ErrNode) const { 229 // Attach bug reports to the leak node. 230 // TODO: Identify the leaked file descriptor. 231 for (SymbolRef LeakedStream : LeakedStreams) { 232 auto R = llvm::make_unique<BugReport>(*LeakBugType, 233 "Opened file is never closed; potential resource leak", ErrNode); 234 R->markInteresting(LeakedStream); 235 C.emitReport(std::move(R)); 236 } 237 } 238 239 bool SimpleStreamChecker::guaranteedNotToCloseFile(const CallEvent &Call) const{ 240 // If it's not in a system header, assume it might close a file. 241 if (!Call.isInSystemHeader()) 242 return false; 243 244 // Handle cases where we know a buffer's /address/ can escape. 245 if (Call.argumentsMayEscape()) 246 return false; 247 248 // Note, even though fclose closes the file, we do not list it here 249 // since the checker is modeling the call. 250 251 return true; 252 } 253 254 // If the pointer we are tracking escaped, do not track the symbol as 255 // we cannot reason about it anymore. 256 ProgramStateRef 257 SimpleStreamChecker::checkPointerEscape(ProgramStateRef State, 258 const InvalidatedSymbols &Escaped, 259 const CallEvent *Call, 260 PointerEscapeKind Kind) const { 261 // If we know that the call cannot close a file, there is nothing to do. 262 if (Kind == PSK_DirectEscapeOnCall && guaranteedNotToCloseFile(*Call)) { 263 return State; 264 } 265 266 for (InvalidatedSymbols::const_iterator I = Escaped.begin(), 267 E = Escaped.end(); 268 I != E; ++I) { 269 SymbolRef Sym = *I; 270 271 // The symbol escaped. Optimistically, assume that the corresponding file 272 // handle will be closed somewhere else. 273 State = State->remove<StreamMap>(Sym); 274 } 275 return State; 276 } 277 278 void SimpleStreamChecker::initIdentifierInfo(ASTContext &Ctx) const { 279 if (IIfopen) 280 return; 281 IIfopen = &Ctx.Idents.get("fopen"); 282 IIfclose = &Ctx.Idents.get("fclose"); 283 } 284 285 void ento::registerSimpleStreamChecker(CheckerManager &mgr) { 286 mgr.registerChecker<SimpleStreamChecker>(); 287 } 288