clang  19.0.0git
SimpleStreamChecker.cpp
Go to the documentation of this file.
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 
23 #include <utility>
24 
25 using namespace clang;
26 using namespace ento;
27 
28 namespace {
29 typedef SmallVector<SymbolRef, 2> SymbolVector;
30 
31 struct StreamState {
32 private:
33  enum Kind { Opened, Closed } K;
34  StreamState(Kind InK) : K(InK) { }
35 
36 public:
37  bool isOpened() const { return K == Opened; }
38  bool isClosed() const { return K == Closed; }
39 
40  static StreamState getOpened() { return StreamState(Opened); }
41  static StreamState getClosed() { return StreamState(Closed); }
42 
43  bool operator==(const StreamState &X) const {
44  return K == X.K;
45  }
46  void Profile(llvm::FoldingSetNodeID &ID) const {
47  ID.AddInteger(K);
48  }
49 };
50 
51 class SimpleStreamChecker : public Checker<check::PostCall,
52  check::PreCall,
53  check::DeadSymbols,
54  check::PointerEscape> {
55  const CallDescription OpenFn{CDM::CLibrary, {"fopen"}, 2};
56  const CallDescription CloseFn{CDM::CLibrary, {"fclose"}, 1};
57 
58  const BugType DoubleCloseBugType{this, "Double fclose",
59  "Unix Stream API Error"};
60  const BugType LeakBugType{this, "Resource Leak", "Unix Stream API Error",
61  /*SuppressOnSink=*/true};
62 
63  void reportDoubleClose(SymbolRef FileDescSym,
64  const CallEvent &Call,
65  CheckerContext &C) const;
66 
67  void reportLeaks(ArrayRef<SymbolRef> LeakedStreams, CheckerContext &C,
68  ExplodedNode *ErrNode) const;
69 
70  bool guaranteedNotToCloseFile(const CallEvent &Call) const;
71 
72 public:
73  /// Process fopen.
74  void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
75  /// Process fclose.
76  void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
77 
78  void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
79 
80  /// Stop tracking addresses which escape.
81  ProgramStateRef checkPointerEscape(ProgramStateRef State,
82  const InvalidatedSymbols &Escaped,
83  const CallEvent *Call,
84  PointerEscapeKind Kind) const;
85 };
86 
87 } // end anonymous namespace
88 
89 /// The state of the checker is a map from tracked stream symbols to their
90 /// state. Let's store it in the ProgramState.
91 REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
92 
93 void SimpleStreamChecker::checkPostCall(const CallEvent &Call,
94  CheckerContext &C) const {
95  if (!OpenFn.matches(Call))
96  return;
97 
98  // Get the symbolic value corresponding to the file handle.
99  SymbolRef FileDesc = Call.getReturnValue().getAsSymbol();
100  if (!FileDesc)
101  return;
102 
103  // Generate the next transition (an edge in the exploded graph).
104  ProgramStateRef State = C.getState();
105  State = State->set<StreamMap>(FileDesc, StreamState::getOpened());
106  C.addTransition(State);
107 }
108 
109 void SimpleStreamChecker::checkPreCall(const CallEvent &Call,
110  CheckerContext &C) const {
111  if (!CloseFn.matches(Call))
112  return;
113 
114  // Get the symbolic value corresponding to the file handle.
115  SymbolRef FileDesc = Call.getArgSVal(0).getAsSymbol();
116  if (!FileDesc)
117  return;
118 
119  // Check if the stream has already been closed.
120  ProgramStateRef State = C.getState();
121  const StreamState *SS = State->get<StreamMap>(FileDesc);
122  if (SS && SS->isClosed()) {
123  reportDoubleClose(FileDesc, Call, C);
124  return;
125  }
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 
132 static bool isLeaked(SymbolRef Sym, const StreamState &SS,
133  bool IsSymDead, ProgramStateRef State) {
134  if (IsSymDead && SS.isOpened()) {
135  // If a symbol is NULL, assume that fopen failed on this path.
136  // A symbol should only be considered leaked if it is non-null.
137  ConstraintManager &CMgr = State->getConstraintManager();
138  ConditionTruthVal OpenFailed = CMgr.isNull(State, Sym);
139  return !OpenFailed.isConstrainedTrue();
140  }
141  return false;
142 }
143 
144 void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
145  CheckerContext &C) const {
146  ProgramStateRef State = C.getState();
147  SymbolVector LeakedStreams;
148  StreamMapTy TrackedStreams = State->get<StreamMap>();
149  for (auto [Sym, StreamStatus] : TrackedStreams) {
150  bool IsSymDead = SymReaper.isDead(Sym);
151 
152  // Collect leaked symbols.
153  if (isLeaked(Sym, StreamStatus, IsSymDead, State))
154  LeakedStreams.push_back(Sym);
155 
156  // Remove the dead symbol from the streams map.
157  if (IsSymDead)
158  State = State->remove<StreamMap>(Sym);
159  }
160 
161  ExplodedNode *N = C.generateNonFatalErrorNode(State);
162  if (!N)
163  return;
164  reportLeaks(LeakedStreams, C, N);
165 }
166 
167 void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym,
168  const CallEvent &Call,
169  CheckerContext &C) const {
170  // We reached a bug, stop exploring the path here by generating a sink.
171  ExplodedNode *ErrNode = C.generateErrorNode();
172  // If we've already reached this node on another path, return.
173  if (!ErrNode)
174  return;
175 
176  // Generate the report.
177  auto R = std::make_unique<PathSensitiveBugReport>(
178  DoubleCloseBugType, "Closing a previously closed file stream", ErrNode);
179  R->addRange(Call.getSourceRange());
180  R->markInteresting(FileDescSym);
181  C.emitReport(std::move(R));
182 }
183 
184 void SimpleStreamChecker::reportLeaks(ArrayRef<SymbolRef> LeakedStreams,
185  CheckerContext &C,
186  ExplodedNode *ErrNode) const {
187  // Attach bug reports to the leak node.
188  // TODO: Identify the leaked file descriptor.
189  for (SymbolRef LeakedStream : LeakedStreams) {
190  auto R = std::make_unique<PathSensitiveBugReport>(
191  LeakBugType, "Opened file is never closed; potential resource leak",
192  ErrNode);
193  R->markInteresting(LeakedStream);
194  C.emitReport(std::move(R));
195  }
196 }
197 
198 bool SimpleStreamChecker::guaranteedNotToCloseFile(const CallEvent &Call) const{
199  // If it's not in a system header, assume it might close a file.
200  if (!Call.isInSystemHeader())
201  return false;
202 
203  // Handle cases where we know a buffer's /address/ can escape.
204  if (Call.argumentsMayEscape())
205  return false;
206 
207  // Note, even though fclose closes the file, we do not list it here
208  // since the checker is modeling the call.
209 
210  return true;
211 }
212 
213 // If the pointer we are tracking escaped, do not track the symbol as
214 // we cannot reason about it anymore.
216 SimpleStreamChecker::checkPointerEscape(ProgramStateRef State,
217  const InvalidatedSymbols &Escaped,
218  const CallEvent *Call,
219  PointerEscapeKind Kind) const {
220  // If we know that the call cannot close a file, there is nothing to do.
221  if (Kind == PSK_DirectEscapeOnCall && guaranteedNotToCloseFile(*Call)) {
222  return State;
223  }
224 
225  for (SymbolRef Sym : Escaped) {
226  // The symbol escaped. Optimistically, assume that the corresponding file
227  // handle will be closed somewhere else.
228  State = State->remove<StreamMap>(Sym);
229  }
230  return State;
231 }
232 
233 void ento::registerSimpleStreamChecker(CheckerManager &mgr) {
234  mgr.registerChecker<SimpleStreamChecker>();
235 }
236 
237 // This checker should be enabled regardless of how language options are set.
238 bool ento::shouldRegisterSimpleStreamChecker(const CheckerManager &mgr) {
239  return true;
240 }
static char ID
Definition: Arena.cpp:183
#define X(type, name)
Definition: Value.h:143
#define REGISTER_MAP_WITH_PROGRAMSTATE(Name, Key, Value)
Declares an immutable map of type NameTy, suitable for placement into the ProgramState.
static bool isLeaked(SymbolRef Sym, const StreamState &SS, bool IsSymDead, ProgramStateRef State)
LineState State
A CallDescription is a pattern that can be used to match calls based on the qualified name and the ar...
@ CLibrary
Match calls to functions from the C standard library.
Represents an abstract call to a function or method along a particular path.
Definition: CallEvent.h:153
CHECKER * registerChecker(AT &&... Args)
Used to register checkers.
bool isConstrainedTrue() const
Return true if the constraint is perfectly constrained to 'true'.
ConditionTruthVal isNull(ProgramStateRef State, SymbolRef Sym)
Convenience method to query the state to see if a symbol is null or not null, or if neither assumptio...
Symbolic value.
Definition: SymExpr.h:30
A class responsible for cleaning up unused symbols.
bool isDead(SymbolRef sym)
Returns whether or not a symbol has been confirmed dead.
PointerEscapeKind
Describes the different reasons a pointer escapes during analysis.
@ PSK_DirectEscapeOnCall
The pointer has been passed to a function call directly.
bool Call(InterpState &S, CodePtr OpPC, const Function *Func, uint32_t VarArgSize)
Definition: Interp.h:2179
The JSON file list parser is used to communicate input to InstallAPI.
bool operator==(const CallGraphNode::CallRecord &LHS, const CallGraphNode::CallRecord &RHS)
Definition: CallGraph.h:223