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. std::unique_ptr<BugType> DoubleCloseBugType;
  48. std::unique_ptr<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) override {
  81. state = state->remove<StreamMap>(sym);
  82. return true;
  83. }
  84. };
  85. } // end anonymous namespace
  86. SimpleStreamChecker::SimpleStreamChecker()
  87. : IIfopen(nullptr), IIfclose(nullptr) {
  88. // Initialize the bug types.
  89. DoubleCloseBugType.reset(
  90. new BugType(this, "Double fclose", "Unix Stream API Error"));
  91. LeakBugType.reset(
  92. new BugType(this, "Resource Leak", "Unix Stream API Error"));
  93. // Sinks are higher importance bugs as well as calls to assert() or exit(0).
  94. LeakBugType->setSuppressOnSink(true);
  95. }
  96. void SimpleStreamChecker::checkPostCall(const CallEvent &Call,
  97. CheckerContext &C) const {
  98. initIdentifierInfo(C.getASTContext());
  99. if (!Call.isGlobalCFunction())
  100. return;
  101. if (Call.getCalleeIdentifier() != IIfopen)
  102. return;
  103. // Get the symbolic value corresponding to the file handle.
  104. SymbolRef FileDesc = Call.getReturnValue().getAsSymbol();
  105. if (!FileDesc)
  106. return;
  107. // Generate the next transition (an edge in the exploded graph).
  108. ProgramStateRef State = C.getState();
  109. State = State->set<StreamMap>(FileDesc, StreamState::getOpened());
  110. C.addTransition(State);
  111. }
  112. void SimpleStreamChecker::checkPreCall(const CallEvent &Call,
  113. CheckerContext &C) const {
  114. initIdentifierInfo(C.getASTContext());
  115. if (!Call.isGlobalCFunction())
  116. return;
  117. if (Call.getCalleeIdentifier() != IIfclose)
  118. return;
  119. if (Call.getNumArgs() != 1)
  120. return;
  121. // Get the symbolic value corresponding to the file handle.
  122. SymbolRef FileDesc = Call.getArgSVal(0).getAsSymbol();
  123. if (!FileDesc)
  124. return;
  125. // Check if the stream has already been closed.
  126. ProgramStateRef State = C.getState();
  127. const StreamState *SS = State->get<StreamMap>(FileDesc);
  128. if (SS && SS->isClosed()) {
  129. reportDoubleClose(FileDesc, Call, C);
  130. return;
  131. }
  132. // Generate the next transition, in which the stream is closed.
  133. State = State->set<StreamMap>(FileDesc, StreamState::getClosed());
  134. C.addTransition(State);
  135. }
  136. static bool isLeaked(SymbolRef Sym, const StreamState &SS,
  137. bool IsSymDead, ProgramStateRef State) {
  138. if (IsSymDead && SS.isOpened()) {
  139. // If a symbol is NULL, assume that fopen failed on this path.
  140. // A symbol should only be considered leaked if it is non-null.
  141. ConstraintManager &CMgr = State->getConstraintManager();
  142. ConditionTruthVal OpenFailed = CMgr.isNull(State, Sym);
  143. return !OpenFailed.isConstrainedTrue();
  144. }
  145. return false;
  146. }
  147. void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
  148. CheckerContext &C) const {
  149. ProgramStateRef State = C.getState();
  150. SymbolVector LeakedStreams;
  151. StreamMapTy TrackedStreams = State->get<StreamMap>();
  152. for (StreamMapTy::iterator I = TrackedStreams.begin(),
  153. E = TrackedStreams.end(); I != E; ++I) {
  154. SymbolRef Sym = I->first;
  155. bool IsSymDead = SymReaper.isDead(Sym);
  156. // Collect leaked symbols.
  157. if (isLeaked(Sym, I->second, IsSymDead, State))
  158. LeakedStreams.push_back(Sym);
  159. // Remove the dead symbol from the streams map.
  160. if (IsSymDead)
  161. State = State->remove<StreamMap>(Sym);
  162. }
  163. ExplodedNode *N = C.addTransition(State);
  164. reportLeaks(LeakedStreams, C, N);
  165. }
  166. void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym,
  167. const CallEvent &Call,
  168. CheckerContext &C) const {
  169. // We reached a bug, stop exploring the path here by generating a sink.
  170. ExplodedNode *ErrNode = C.generateSink();
  171. // If we've already reached this node on another path, return.
  172. if (!ErrNode)
  173. return;
  174. // Generate the report.
  175. BugReport *R = new BugReport(*DoubleCloseBugType,
  176. "Closing a previously closed file stream", ErrNode);
  177. R->addRange(Call.getSourceRange());
  178. R->markInteresting(FileDescSym);
  179. C.emitReport(R);
  180. }
  181. void SimpleStreamChecker::reportLeaks(SymbolVector LeakedStreams,
  182. CheckerContext &C,
  183. ExplodedNode *ErrNode) const {
  184. // Attach bug reports to the leak node.
  185. // TODO: Identify the leaked file descriptor.
  186. for (SmallVectorImpl<SymbolRef>::iterator
  187. I = LeakedStreams.begin(), E = LeakedStreams.end(); I != E; ++I) {
  188. BugReport *R = new BugReport(*LeakBugType,
  189. "Opened file is never closed; potential resource leak", ErrNode);
  190. R->markInteresting(*I);
  191. C.emitReport(R);
  192. }
  193. }
  194. bool SimpleStreamChecker::guaranteedNotToCloseFile(const CallEvent &Call) const{
  195. // If it's not in a system header, assume it might close a file.
  196. if (!Call.isInSystemHeader())
  197. return false;
  198. // Handle cases where we know a buffer's /address/ can escape.
  199. if (Call.argumentsMayEscape())
  200. return false;
  201. // Note, even though fclose closes the file, we do not list it here
  202. // since the checker is modeling the call.
  203. return true;
  204. }
  205. // If the pointer we are tracking escaped, do not track the symbol as
  206. // we cannot reason about it anymore.
  207. ProgramStateRef
  208. SimpleStreamChecker::checkPointerEscape(ProgramStateRef State,
  209. const InvalidatedSymbols &Escaped,
  210. const CallEvent *Call,
  211. PointerEscapeKind Kind) const {
  212. // If we know that the call cannot close a file, there is nothing to do.
  213. if (Kind == PSK_DirectEscapeOnCall && guaranteedNotToCloseFile(*Call)) {
  214. return State;
  215. }
  216. for (InvalidatedSymbols::const_iterator I = Escaped.begin(),
  217. E = Escaped.end();
  218. I != E; ++I) {
  219. SymbolRef Sym = *I;
  220. // The symbol escaped. Optimistically, assume that the corresponding file
  221. // handle will be closed somewhere else.
  222. State = State->remove<StreamMap>(Sym);
  223. }
  224. return State;
  225. }
  226. void SimpleStreamChecker::initIdentifierInfo(ASTContext &Ctx) const {
  227. if (IIfopen)
  228. return;
  229. IIfopen = &Ctx.Idents.get("fopen");
  230. IIfclose = &Ctx.Idents.get("fclose");
  231. }
  232. void ento::registerSimpleStreamChecker(CheckerManager &mgr) {
  233. mgr.registerChecker<SimpleStreamChecker>();
  234. }