SimpleStreamChecker.cpp 9.1 KB

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