SimpleStreamChecker.cpp 9.5 KB

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