clang 17.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
25using namespace clang;
26using namespace ento;
27
28namespace {
29typedef SmallVector<SymbolRef, 2> SymbolVector;
30
31struct StreamState {
32private:
33 enum Kind { Opened, Closed } K;
34 StreamState(Kind InK) : K(InK) { }
35
36public:
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
51class SimpleStreamChecker : public Checker<check::PostCall,
52 check::PreCall,
53 check::DeadSymbols,
54 check::PointerEscape> {
55 CallDescription OpenFn, CloseFn;
56
57 std::unique_ptr<BugType> DoubleCloseBugType;
58 std::unique_ptr<BugType> LeakBugType;
59
60 void reportDoubleClose(SymbolRef FileDescSym,
61 const CallEvent &Call,
62 CheckerContext &C) const;
63
64 void reportLeaks(ArrayRef<SymbolRef> LeakedStreams, CheckerContext &C,
65 ExplodedNode *ErrNode) const;
66
67 bool guaranteedNotToCloseFile(const CallEvent &Call) const;
68
69public:
70 SimpleStreamChecker();
71
72 /// Process fopen.
73 void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
74 /// Process fclose.
75 void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
76
77 void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
78
79 /// Stop tracking addresses which escape.
80 ProgramStateRef checkPointerEscape(ProgramStateRef State,
81 const InvalidatedSymbols &Escaped,
82 const CallEvent *Call,
83 PointerEscapeKind Kind) const;
84};
85
86} // end anonymous namespace
87
88/// The state of the checker is a map from tracked stream symbols to their
89/// state. Let's store it in the ProgramState.
90REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
91
92SimpleStreamChecker::SimpleStreamChecker()
93 : OpenFn({"fopen"}), CloseFn({"fclose"}, 1) {
94 // Initialize the bug types.
95 DoubleCloseBugType.reset(
96 new BugType(this, "Double fclose", "Unix Stream API Error"));
97
98 // Sinks are higher importance bugs as well as calls to assert() or exit(0).
99 LeakBugType.reset(
100 new BugType(this, "Resource Leak", "Unix Stream API Error",
101 /*SuppressOnSink=*/true));
102}
103
104void SimpleStreamChecker::checkPostCall(const CallEvent &Call,
105 CheckerContext &C) const {
106 if (!Call.isGlobalCFunction())
107 return;
108
109 if (!OpenFn.matches(Call))
110 return;
111
112 // Get the symbolic value corresponding to the file handle.
113 SymbolRef FileDesc = Call.getReturnValue().getAsSymbol();
114 if (!FileDesc)
115 return;
116
117 // Generate the next transition (an edge in the exploded graph).
118 ProgramStateRef State = C.getState();
119 State = State->set<StreamMap>(FileDesc, StreamState::getOpened());
120 C.addTransition(State);
121}
122
123void SimpleStreamChecker::checkPreCall(const CallEvent &Call,
124 CheckerContext &C) const {
125 if (!Call.isGlobalCFunction())
126 return;
127
128 if (!CloseFn.matches(Call))
129 return;
130
131 // Get the symbolic value corresponding to the file handle.
132 SymbolRef FileDesc = Call.getArgSVal(0).getAsSymbol();
133 if (!FileDesc)
134 return;
135
136 // Check if the stream has already been closed.
137 ProgramStateRef State = C.getState();
138 const StreamState *SS = State->get<StreamMap>(FileDesc);
139 if (SS && SS->isClosed()) {
140 reportDoubleClose(FileDesc, Call, C);
141 return;
142 }
143
144 // Generate the next transition, in which the stream is closed.
145 State = State->set<StreamMap>(FileDesc, StreamState::getClosed());
146 C.addTransition(State);
147}
148
149static bool isLeaked(SymbolRef Sym, const StreamState &SS,
150 bool IsSymDead, ProgramStateRef State) {
151 if (IsSymDead && SS.isOpened()) {
152 // If a symbol is NULL, assume that fopen failed on this path.
153 // A symbol should only be considered leaked if it is non-null.
154 ConstraintManager &CMgr = State->getConstraintManager();
155 ConditionTruthVal OpenFailed = CMgr.isNull(State, Sym);
156 return !OpenFailed.isConstrainedTrue();
157 }
158 return false;
159}
160
161void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
162 CheckerContext &C) const {
163 ProgramStateRef State = C.getState();
164 SymbolVector LeakedStreams;
165 StreamMapTy TrackedStreams = State->get<StreamMap>();
166 for (StreamMapTy::iterator I = TrackedStreams.begin(),
167 E = TrackedStreams.end(); I != E; ++I) {
168 SymbolRef Sym = I->first;
169 bool IsSymDead = SymReaper.isDead(Sym);
170
171 // Collect leaked symbols.
172 if (isLeaked(Sym, I->second, IsSymDead, State))
173 LeakedStreams.push_back(Sym);
174
175 // Remove the dead symbol from the streams map.
176 if (IsSymDead)
177 State = State->remove<StreamMap>(Sym);
178 }
179
180 ExplodedNode *N = C.generateNonFatalErrorNode(State);
181 if (!N)
182 return;
183 reportLeaks(LeakedStreams, C, N);
184}
185
186void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym,
187 const CallEvent &Call,
188 CheckerContext &C) const {
189 // We reached a bug, stop exploring the path here by generating a sink.
190 ExplodedNode *ErrNode = C.generateErrorNode();
191 // If we've already reached this node on another path, return.
192 if (!ErrNode)
193 return;
194
195 // Generate the report.
196 auto R = std::make_unique<PathSensitiveBugReport>(
197 *DoubleCloseBugType, "Closing a previously closed file stream", ErrNode);
198 R->addRange(Call.getSourceRange());
199 R->markInteresting(FileDescSym);
200 C.emitReport(std::move(R));
201}
202
203void SimpleStreamChecker::reportLeaks(ArrayRef<SymbolRef> LeakedStreams,
205 ExplodedNode *ErrNode) const {
206 // Attach bug reports to the leak node.
207 // TODO: Identify the leaked file descriptor.
208 for (SymbolRef LeakedStream : LeakedStreams) {
209 auto R = std::make_unique<PathSensitiveBugReport>(
210 *LeakBugType, "Opened file is never closed; potential resource leak",
211 ErrNode);
212 R->markInteresting(LeakedStream);
213 C.emitReport(std::move(R));
214 }
215}
216
217bool SimpleStreamChecker::guaranteedNotToCloseFile(const CallEvent &Call) const{
218 // If it's not in a system header, assume it might close a file.
219 if (!Call.isInSystemHeader())
220 return false;
221
222 // Handle cases where we know a buffer's /address/ can escape.
223 if (Call.argumentsMayEscape())
224 return false;
225
226 // Note, even though fclose closes the file, we do not list it here
227 // since the checker is modeling the call.
228
229 return true;
230}
231
232// If the pointer we are tracking escaped, do not track the symbol as
233// we cannot reason about it anymore.
235SimpleStreamChecker::checkPointerEscape(ProgramStateRef State,
236 const InvalidatedSymbols &Escaped,
237 const CallEvent *Call,
238 PointerEscapeKind Kind) const {
239 // If we know that the call cannot close a file, there is nothing to do.
240 if (Kind == PSK_DirectEscapeOnCall && guaranteedNotToCloseFile(*Call)) {
241 return State;
242 }
243
244 for (InvalidatedSymbols::const_iterator I = Escaped.begin(),
245 E = Escaped.end();
246 I != E; ++I) {
247 SymbolRef Sym = *I;
248
249 // The symbol escaped. Optimistically, assume that the corresponding file
250 // handle will be closed somewhere else.
251 State = State->remove<StreamMap>(Sym);
252 }
253 return State;
254}
255
256void ento::registerSimpleStreamChecker(CheckerManager &mgr) {
257 mgr.registerChecker<SimpleStreamChecker>();
258}
259
260// This checker should be enabled regardless of how language options are set.
261bool ento::shouldRegisterSimpleStreamChecker(const CheckerManager &mgr) {
262 return true;
263}
static CompilationDatabasePluginRegistry::Add< FixedCompilationDatabasePlugin > X("fixed-compilation-database", "Reads plain-text flags file")
#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)
This class represents a description of a function call using the number of arguments and the name of ...
Represents an abstract call to a function or method along a particular path.
Definition: CallEvent.h:149
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:29
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 &PC, const Function *Func)
Definition: Interp.h:1494
bool operator==(const CallGraphNode::CallRecord &LHS, const CallGraphNode::CallRecord &RHS)
Definition: CallGraph.h:207
@ C
Languages that the frontend can parse and compile.