blob: ab4b4d3bd91be719518e9525a493d7c4ce388b8a [file] [log] [blame]
Anna Zaksbe70d4d2012-10-29 22:51:50 +00001//===-- 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
18#include "ClangSACheckers.h"
Anna Zaksbe70d4d2012-10-29 22:51:50 +000019#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
Chandler Carruth3a022472012-12-04 09:13:33 +000020#include "clang/StaticAnalyzer/Core/Checker.h"
Jordan Rose58e82932012-11-02 23:49:35 +000021#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
Anna Zaksbe70d4d2012-10-29 22:51:50 +000022#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
Benjamin Kramercfeacf52016-05-27 14:27:13 +000023#include <utility>
Anna Zaksbe70d4d2012-10-29 22:51:50 +000024
25using namespace clang;
26using namespace ento;
27
28namespace {
Dmitri Gribenkof8579502013-01-12 19:30:44 +000029typedef SmallVector<SymbolRef, 2> SymbolVector;
Anna Zaksbe70d4d2012-10-29 22:51:50 +000030
31struct StreamState {
Anna Zaks1e80d8b2012-10-31 02:32:41 +000032private:
Anna Zaksbe70d4d2012-10-29 22:51:50 +000033 enum Kind { Opened, Closed } K;
Anna Zaksbe70d4d2012-10-29 22:51:50 +000034 StreamState(Kind InK) : K(InK) { }
35
Anna Zaks1e80d8b2012-10-31 02:32:41 +000036public:
Anna Zaksbe70d4d2012-10-29 22:51:50 +000037 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
Jordan Rose58e82932012-11-02 23:49:35 +000051class SimpleStreamChecker : public Checker<check::PostCall,
52 check::PreCall,
Anna Zaks2ed51252012-11-06 04:20:57 +000053 check::DeadSymbols,
Anna Zaks0dffbd62012-12-22 00:18:39 +000054 check::PointerEscape> {
Gabor Horvath343730c2016-01-22 22:32:46 +000055 CallDescription OpenFn, CloseFn;
Anna Zaksbe70d4d2012-10-29 22:51:50 +000056
Ahmed Charlesb8984322014-03-07 20:03:18 +000057 std::unique_ptr<BugType> DoubleCloseBugType;
58 std::unique_ptr<BugType> LeakBugType;
Anna Zaksbe70d4d2012-10-29 22:51:50 +000059
Anna Zaksbe70d4d2012-10-29 22:51:50 +000060 void reportDoubleClose(SymbolRef FileDescSym,
Jordan Rose58e82932012-11-02 23:49:35 +000061 const CallEvent &Call,
Anna Zaksbe70d4d2012-10-29 22:51:50 +000062 CheckerContext &C) const;
63
Benjamin Kramer22c68ef2014-09-11 14:13:49 +000064 void reportLeaks(ArrayRef<SymbolRef> LeakedStreams, CheckerContext &C,
Jordan Rose2b213722012-11-01 00:18:41 +000065 ExplodedNode *ErrNode) const;
Anna Zaksbe70d4d2012-10-29 22:51:50 +000066
Anna Zaks2ed51252012-11-06 04:20:57 +000067 bool guaranteedNotToCloseFile(const CallEvent &Call) const;
68
Anna Zaksbe70d4d2012-10-29 22:51:50 +000069public:
Anna Zaks1e80d8b2012-10-31 02:32:41 +000070 SimpleStreamChecker();
Anna Zaksbe70d4d2012-10-29 22:51:50 +000071
72 /// Process fopen.
Jordan Rose58e82932012-11-02 23:49:35 +000073 void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
Anna Zaksbe70d4d2012-10-29 22:51:50 +000074 /// Process fclose.
Jordan Rose58e82932012-11-02 23:49:35 +000075 void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
Anna Zaksbe70d4d2012-10-29 22:51:50 +000076
77 void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
Anna Zaks2ed51252012-11-06 04:20:57 +000078
Anna Zaks0dffbd62012-12-22 00:18:39 +000079 /// Stop tracking addresses which escape.
80 ProgramStateRef checkPointerEscape(ProgramStateRef State,
81 const InvalidatedSymbols &Escaped,
Anna Zaksacdc13c2013-02-07 23:05:43 +000082 const CallEvent *Call,
83 PointerEscapeKind Kind) const;
Anna Zaksbe70d4d2012-10-29 22:51:50 +000084};
85
86} // end anonymous namespace
87
88/// The state of the checker is a map from tracked stream symbols to their
Anna Zaks4afaaf22012-10-30 04:17:18 +000089/// state. Let's store it in the ProgramState.
90REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
Anna Zaksbe70d4d2012-10-29 22:51:50 +000091
Anna Zaks2ed51252012-11-06 04:20:57 +000092namespace {
David Blaikie903c2932015-08-13 22:50:09 +000093class StopTrackingCallback final : public SymbolVisitor {
Anna Zaks2ed51252012-11-06 04:20:57 +000094 ProgramStateRef state;
95public:
Benjamin Kramercfeacf52016-05-27 14:27:13 +000096 StopTrackingCallback(ProgramStateRef st) : state(std::move(st)) {}
Anna Zaks2ed51252012-11-06 04:20:57 +000097 ProgramStateRef getState() const { return state; }
98
Craig Topperfb6b25b2014-03-15 04:29:04 +000099 bool VisitSymbol(SymbolRef sym) override {
Anna Zaks2ed51252012-11-06 04:20:57 +0000100 state = state->remove<StreamMap>(sym);
101 return true;
102 }
103};
104} // end anonymous namespace
105
Craig Topper0dbb7832014-05-27 02:45:47 +0000106SimpleStreamChecker::SimpleStreamChecker()
Gabor Horvath343730c2016-01-22 22:32:46 +0000107 : OpenFn("fopen"), CloseFn("fclose", 1) {
Anna Zaks1e80d8b2012-10-31 02:32:41 +0000108 // Initialize the bug types.
Alexander Kornienko4aca9b12014-02-11 21:49:21 +0000109 DoubleCloseBugType.reset(
110 new BugType(this, "Double fclose", "Unix Stream API Error"));
Anna Zaks1e80d8b2012-10-31 02:32:41 +0000111
Alexander Kornienko4aca9b12014-02-11 21:49:21 +0000112 LeakBugType.reset(
113 new BugType(this, "Resource Leak", "Unix Stream API Error"));
Anna Zaks1e80d8b2012-10-31 02:32:41 +0000114 // Sinks are higher importance bugs as well as calls to assert() or exit(0).
115 LeakBugType->setSuppressOnSink(true);
116}
117
Jordan Rose58e82932012-11-02 23:49:35 +0000118void SimpleStreamChecker::checkPostCall(const CallEvent &Call,
Anna Zaksbe70d4d2012-10-29 22:51:50 +0000119 CheckerContext &C) const {
Jordan Rose58e82932012-11-02 23:49:35 +0000120 if (!Call.isGlobalCFunction())
121 return;
122
Gabor Horvath343730c2016-01-22 22:32:46 +0000123 if (!Call.isCalled(OpenFn))
Anna Zaksbe70d4d2012-10-29 22:51:50 +0000124 return;
125
126 // Get the symbolic value corresponding to the file handle.
Jordan Rose58e82932012-11-02 23:49:35 +0000127 SymbolRef FileDesc = Call.getReturnValue().getAsSymbol();
Anna Zaksbe70d4d2012-10-29 22:51:50 +0000128 if (!FileDesc)
129 return;
130
131 // Generate the next transition (an edge in the exploded graph).
132 ProgramStateRef State = C.getState();
133 State = State->set<StreamMap>(FileDesc, StreamState::getOpened());
134 C.addTransition(State);
135}
136
Jordan Rose58e82932012-11-02 23:49:35 +0000137void SimpleStreamChecker::checkPreCall(const CallEvent &Call,
Anna Zaksbe70d4d2012-10-29 22:51:50 +0000138 CheckerContext &C) const {
Jordan Rose58e82932012-11-02 23:49:35 +0000139 if (!Call.isGlobalCFunction())
140 return;
141
Gabor Horvath343730c2016-01-22 22:32:46 +0000142 if (!Call.isCalled(CloseFn))
Anna Zaksbe70d4d2012-10-29 22:51:50 +0000143 return;
144
145 // Get the symbolic value corresponding to the file handle.
Jordan Rose58e82932012-11-02 23:49:35 +0000146 SymbolRef FileDesc = Call.getArgSVal(0).getAsSymbol();
Anna Zaksbe70d4d2012-10-29 22:51:50 +0000147 if (!FileDesc)
148 return;
149
150 // Check if the stream has already been closed.
151 ProgramStateRef State = C.getState();
152 const StreamState *SS = State->get<StreamMap>(FileDesc);
Anna Zaksa57e8ff2012-10-31 22:17:48 +0000153 if (SS && SS->isClosed()) {
Anna Zaksbe70d4d2012-10-29 22:51:50 +0000154 reportDoubleClose(FileDesc, Call, C);
Anna Zaksa57e8ff2012-10-31 22:17:48 +0000155 return;
156 }
Anna Zaksbe70d4d2012-10-29 22:51:50 +0000157
158 // Generate the next transition, in which the stream is closed.
159 State = State->set<StreamMap>(FileDesc, StreamState::getClosed());
160 C.addTransition(State);
161}
162
Anna Zaksda27efe2012-11-02 21:30:04 +0000163static bool isLeaked(SymbolRef Sym, const StreamState &SS,
164 bool IsSymDead, ProgramStateRef State) {
165 if (IsSymDead && SS.isOpened()) {
166 // If a symbol is NULL, assume that fopen failed on this path.
167 // A symbol should only be considered leaked if it is non-null.
168 ConstraintManager &CMgr = State->getConstraintManager();
169 ConditionTruthVal OpenFailed = CMgr.isNull(State, Sym);
170 return !OpenFailed.isConstrainedTrue();
171 }
172 return false;
173}
174
Anna Zaksbe70d4d2012-10-29 22:51:50 +0000175void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
176 CheckerContext &C) const {
177 ProgramStateRef State = C.getState();
Anna Zaksbe70d4d2012-10-29 22:51:50 +0000178 SymbolVector LeakedStreams;
Anna Zaksda27efe2012-11-02 21:30:04 +0000179 StreamMapTy TrackedStreams = State->get<StreamMap>();
Anna Zaks302da832012-10-30 04:17:40 +0000180 for (StreamMapTy::iterator I = TrackedStreams.begin(),
Jordan Rose14fe9f32012-11-01 00:18:27 +0000181 E = TrackedStreams.end(); I != E; ++I) {
Anna Zaksbe70d4d2012-10-29 22:51:50 +0000182 SymbolRef Sym = I->first;
Anna Zaksda27efe2012-11-02 21:30:04 +0000183 bool IsSymDead = SymReaper.isDead(Sym);
Anna Zaksbe70d4d2012-10-29 22:51:50 +0000184
Anna Zaksda27efe2012-11-02 21:30:04 +0000185 // Collect leaked symbols.
186 if (isLeaked(Sym, I->second, IsSymDead, State))
187 LeakedStreams.push_back(Sym);
188
189 // Remove the dead symbol from the streams map.
190 if (IsSymDead)
Anna Zaksbe70d4d2012-10-29 22:51:50 +0000191 State = State->remove<StreamMap>(Sym);
Anna Zaksbe70d4d2012-10-29 22:51:50 +0000192 }
193
Devin Coughline39bd402015-09-16 22:03:05 +0000194 ExplodedNode *N = C.generateNonFatalErrorNode(State);
195 if (!N)
196 return;
Anna Zaks1e80d8b2012-10-31 02:32:41 +0000197 reportLeaks(LeakedStreams, C, N);
Anna Zaksbe70d4d2012-10-29 22:51:50 +0000198}
199
200void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym,
Jordan Rose58e82932012-11-02 23:49:35 +0000201 const CallEvent &Call,
Anna Zaksbe70d4d2012-10-29 22:51:50 +0000202 CheckerContext &C) const {
203 // We reached a bug, stop exploring the path here by generating a sink.
Devin Coughline39bd402015-09-16 22:03:05 +0000204 ExplodedNode *ErrNode = C.generateErrorNode();
Anna Zaks1e80d8b2012-10-31 02:32:41 +0000205 // If we've already reached this node on another path, return.
Anna Zaksbe70d4d2012-10-29 22:51:50 +0000206 if (!ErrNode)
207 return;
208
Anna Zaksbe70d4d2012-10-29 22:51:50 +0000209 // Generate the report.
Aaron Ballman8d3a7a52015-06-23 13:15:32 +0000210 auto R = llvm::make_unique<BugReport>(*DoubleCloseBugType,
Anna Zaksbe70d4d2012-10-29 22:51:50 +0000211 "Closing a previously closed file stream", ErrNode);
Jordan Rose58e82932012-11-02 23:49:35 +0000212 R->addRange(Call.getSourceRange());
Anna Zaksbe70d4d2012-10-29 22:51:50 +0000213 R->markInteresting(FileDescSym);
Aaron Ballman8d3a7a52015-06-23 13:15:32 +0000214 C.emitReport(std::move(R));
Anna Zaksbe70d4d2012-10-29 22:51:50 +0000215}
216
Benjamin Kramer22c68ef2014-09-11 14:13:49 +0000217void SimpleStreamChecker::reportLeaks(ArrayRef<SymbolRef> LeakedStreams,
218 CheckerContext &C,
219 ExplodedNode *ErrNode) const {
Anna Zaksbe70d4d2012-10-29 22:51:50 +0000220 // Attach bug reports to the leak node.
Anna Zaks92d96602012-10-30 04:18:21 +0000221 // TODO: Identify the leaked file descriptor.
Benjamin Kramer22c68ef2014-09-11 14:13:49 +0000222 for (SymbolRef LeakedStream : LeakedStreams) {
Aaron Ballman8d3a7a52015-06-23 13:15:32 +0000223 auto R = llvm::make_unique<BugReport>(*LeakBugType,
Anna Zaksbe70d4d2012-10-29 22:51:50 +0000224 "Opened file is never closed; potential resource leak", ErrNode);
Benjamin Kramer22c68ef2014-09-11 14:13:49 +0000225 R->markInteresting(LeakedStream);
Aaron Ballman8d3a7a52015-06-23 13:15:32 +0000226 C.emitReport(std::move(R));
Anna Zaksbe70d4d2012-10-29 22:51:50 +0000227 }
Anna Zaksbe70d4d2012-10-29 22:51:50 +0000228}
229
Anna Zaks2ed51252012-11-06 04:20:57 +0000230bool SimpleStreamChecker::guaranteedNotToCloseFile(const CallEvent &Call) const{
231 // If it's not in a system header, assume it might close a file.
232 if (!Call.isInSystemHeader())
233 return false;
234
235 // Handle cases where we know a buffer's /address/ can escape.
236 if (Call.argumentsMayEscape())
237 return false;
238
239 // Note, even though fclose closes the file, we do not list it here
240 // since the checker is modeling the call.
241
242 return true;
243}
244
Anna Zaks0dffbd62012-12-22 00:18:39 +0000245// If the pointer we are tracking escaped, do not track the symbol as
Anna Zaks2ed51252012-11-06 04:20:57 +0000246// we cannot reason about it anymore.
247ProgramStateRef
Anna Zaks0dffbd62012-12-22 00:18:39 +0000248SimpleStreamChecker::checkPointerEscape(ProgramStateRef State,
249 const InvalidatedSymbols &Escaped,
Anna Zaksacdc13c2013-02-07 23:05:43 +0000250 const CallEvent *Call,
251 PointerEscapeKind Kind) const {
Anna Zaks0dffbd62012-12-22 00:18:39 +0000252 // If we know that the call cannot close a file, there is nothing to do.
Jordan Rose757fbb02013-05-10 17:07:16 +0000253 if (Kind == PSK_DirectEscapeOnCall && guaranteedNotToCloseFile(*Call)) {
Anna Zaks2ed51252012-11-06 04:20:57 +0000254 return State;
Anna Zaksacdc13c2013-02-07 23:05:43 +0000255 }
Anna Zaks2ed51252012-11-06 04:20:57 +0000256
Anna Zaks0dffbd62012-12-22 00:18:39 +0000257 for (InvalidatedSymbols::const_iterator I = Escaped.begin(),
258 E = Escaped.end();
259 I != E; ++I) {
260 SymbolRef Sym = *I;
Anna Zaks2ed51252012-11-06 04:20:57 +0000261
Anna Zaks2ed51252012-11-06 04:20:57 +0000262 // The symbol escaped. Optimistically, assume that the corresponding file
263 // handle will be closed somewhere else.
Anna Zaks0dffbd62012-12-22 00:18:39 +0000264 State = State->remove<StreamMap>(Sym);
Anna Zaks2ed51252012-11-06 04:20:57 +0000265 }
266 return State;
267}
268
Anna Zaksbe70d4d2012-10-29 22:51:50 +0000269void ento::registerSimpleStreamChecker(CheckerManager &mgr) {
270 mgr.registerChecker<SimpleStreamChecker>();
271}