SimpleStreamChecker.cpp 9.5 KB

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