1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
18 | #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
19 | #include "clang/StaticAnalyzer/Core/Checker.h" |
20 | #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" |
21 | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
22 | #include <utility> |
23 | |
24 | using namespace clang; |
25 | using namespace ento; |
26 | |
27 | namespace { |
28 | typedef SmallVector<SymbolRef, 2> SymbolVector; |
29 | |
30 | struct StreamState { |
31 | private: |
32 | enum Kind { Opened, Closed } K; |
33 | StreamState(Kind InK) : K(InK) { } |
34 | |
35 | public: |
36 | bool isOpened() const { return K == Opened; } |
37 | bool isClosed() const { return K == Closed; } |
38 | |
39 | static StreamState getOpened() { return StreamState(Opened); } |
40 | static StreamState getClosed() { return StreamState(Closed); } |
41 | |
42 | bool operator==(const StreamState &X) const { |
43 | return K == X.K; |
44 | } |
45 | void Profile(llvm::FoldingSetNodeID &ID) const { |
46 | ID.AddInteger(K); |
47 | } |
48 | }; |
49 | |
50 | class SimpleStreamChecker : public Checker<check::PostCall, |
51 | check::PreCall, |
52 | check::DeadSymbols, |
53 | check::PointerEscape> { |
54 | CallDescription OpenFn, CloseFn; |
55 | |
56 | std::unique_ptr<BugType> DoubleCloseBugType; |
57 | std::unique_ptr<BugType> LeakBugType; |
58 | |
59 | void reportDoubleClose(SymbolRef FileDescSym, |
60 | const CallEvent &Call, |
61 | CheckerContext &C) const; |
62 | |
63 | void reportLeaks(ArrayRef<SymbolRef> LeakedStreams, CheckerContext &C, |
64 | ExplodedNode *ErrNode) const; |
65 | |
66 | bool guaranteedNotToCloseFile(const CallEvent &Call) const; |
67 | |
68 | public: |
69 | SimpleStreamChecker(); |
70 | |
71 | |
72 | void checkPostCall(const CallEvent &Call, CheckerContext &C) const; |
73 | |
74 | void checkPreCall(const CallEvent &Call, CheckerContext &C) const; |
75 | |
76 | void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; |
77 | |
78 | |
79 | ProgramStateRef checkPointerEscape(ProgramStateRef State, |
80 | const InvalidatedSymbols &Escaped, |
81 | const CallEvent *Call, |
82 | PointerEscapeKind Kind) const; |
83 | }; |
84 | |
85 | } |
86 | |
87 | |
88 | |
89 | REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) |
90 | |
91 | namespace { |
92 | class StopTrackingCallback final : public SymbolVisitor { |
93 | ProgramStateRef state; |
94 | public: |
95 | StopTrackingCallback(ProgramStateRef st) : state(std::move(st)) {} |
96 | ProgramStateRef getState() const { return state; } |
97 | |
98 | bool VisitSymbol(SymbolRef sym) override { |
99 | state = state->remove<StreamMap>(sym); |
100 | return true; |
101 | } |
102 | }; |
103 | } |
104 | |
105 | SimpleStreamChecker::SimpleStreamChecker() |
106 | : OpenFn("fopen"), CloseFn("fclose", 1) { |
107 | |
108 | DoubleCloseBugType.reset( |
109 | new BugType(this, "Double fclose", "Unix Stream API Error")); |
110 | |
111 | |
112 | LeakBugType.reset( |
113 | new BugType(this, "Resource Leak", "Unix Stream API Error", |
114 | )); |
115 | } |
116 | |
117 | void SimpleStreamChecker::checkPostCall(const CallEvent &Call, |
118 | CheckerContext &C) const { |
119 | if (!Call.isGlobalCFunction()) |
120 | return; |
121 | |
122 | if (!Call.isCalled(OpenFn)) |
123 | return; |
124 | |
125 | |
126 | SymbolRef FileDesc = Call.getReturnValue().getAsSymbol(); |
127 | if (!FileDesc) |
128 | return; |
129 | |
130 | |
131 | ProgramStateRef State = C.getState(); |
132 | State = State->set<StreamMap>(FileDesc, StreamState::getOpened()); |
133 | C.addTransition(State); |
134 | } |
135 | |
136 | void SimpleStreamChecker::checkPreCall(const CallEvent &Call, |
137 | CheckerContext &C) const { |
138 | if (!Call.isGlobalCFunction()) |
139 | return; |
140 | |
141 | if (!Call.isCalled(CloseFn)) |
142 | return; |
143 | |
144 | |
145 | SymbolRef FileDesc = Call.getArgSVal(0).getAsSymbol(); |
146 | if (!FileDesc) |
147 | return; |
148 | |
149 | |
150 | ProgramStateRef State = C.getState(); |
151 | const StreamState *SS = State->get<StreamMap>(FileDesc); |
152 | if (SS && SS->isClosed()) { |
153 | reportDoubleClose(FileDesc, Call, C); |
154 | return; |
155 | } |
156 | |
157 | |
158 | State = State->set<StreamMap>(FileDesc, StreamState::getClosed()); |
159 | C.addTransition(State); |
160 | } |
161 | |
162 | static bool isLeaked(SymbolRef Sym, const StreamState &SS, |
163 | bool IsSymDead, ProgramStateRef State) { |
164 | if (IsSymDead && SS.isOpened()) { |
165 | |
166 | |
167 | ConstraintManager &CMgr = State->getConstraintManager(); |
168 | ConditionTruthVal OpenFailed = CMgr.isNull(State, Sym); |
169 | return !OpenFailed.isConstrainedTrue(); |
170 | } |
171 | return false; |
172 | } |
173 | |
174 | void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, |
175 | CheckerContext &C) const { |
176 | ProgramStateRef State = C.getState(); |
177 | SymbolVector LeakedStreams; |
178 | StreamMapTy TrackedStreams = State->get<StreamMap>(); |
179 | for (StreamMapTy::iterator I = TrackedStreams.begin(), |
180 | E = TrackedStreams.end(); I != E; ++I) { |
181 | SymbolRef Sym = I->first; |
182 | bool IsSymDead = SymReaper.isDead(Sym); |
183 | |
184 | |
185 | if (isLeaked(Sym, I->second, IsSymDead, State)) |
186 | LeakedStreams.push_back(Sym); |
187 | |
188 | |
189 | if (IsSymDead) |
190 | State = State->remove<StreamMap>(Sym); |
191 | } |
192 | |
193 | ExplodedNode *N = C.generateNonFatalErrorNode(State); |
194 | if (!N) |
195 | return; |
196 | reportLeaks(LeakedStreams, C, N); |
197 | } |
198 | |
199 | void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym, |
200 | const CallEvent &Call, |
201 | CheckerContext &C) const { |
202 | |
203 | ExplodedNode *ErrNode = C.generateErrorNode(); |
204 | |
205 | if (!ErrNode) |
206 | return; |
207 | |
208 | |
209 | auto R = llvm::make_unique<BugReport>(*DoubleCloseBugType, |
210 | "Closing a previously closed file stream", ErrNode); |
211 | R->addRange(Call.getSourceRange()); |
212 | R->markInteresting(FileDescSym); |
213 | C.emitReport(std::move(R)); |
214 | } |
215 | |
216 | void SimpleStreamChecker::reportLeaks(ArrayRef<SymbolRef> LeakedStreams, |
217 | CheckerContext &C, |
218 | ExplodedNode *ErrNode) const { |
219 | |
220 | |
221 | for (SymbolRef LeakedStream : LeakedStreams) { |
222 | auto R = llvm::make_unique<BugReport>(*LeakBugType, |
223 | "Opened file is never closed; potential resource leak", ErrNode); |
224 | R->markInteresting(LeakedStream); |
225 | C.emitReport(std::move(R)); |
226 | } |
227 | } |
228 | |
229 | bool SimpleStreamChecker::guaranteedNotToCloseFile(const CallEvent &Call) const{ |
230 | |
231 | if (!Call.isInSystemHeader()) |
232 | return false; |
233 | |
234 | |
235 | if (Call.argumentsMayEscape()) |
236 | return false; |
237 | |
238 | |
239 | |
240 | |
241 | return true; |
242 | } |
243 | |
244 | |
245 | |
246 | ProgramStateRef |
247 | SimpleStreamChecker::checkPointerEscape(ProgramStateRef State, |
248 | const InvalidatedSymbols &Escaped, |
249 | const CallEvent *Call, |
250 | PointerEscapeKind Kind) const { |
251 | |
252 | if (Kind == PSK_DirectEscapeOnCall && guaranteedNotToCloseFile(*Call)) { |
253 | return State; |
254 | } |
255 | |
256 | for (InvalidatedSymbols::const_iterator I = Escaped.begin(), |
257 | E = Escaped.end(); |
258 | I != E; ++I) { |
259 | SymbolRef Sym = *I; |
260 | |
261 | |
262 | |
263 | State = State->remove<StreamMap>(Sym); |
264 | } |
265 | return State; |
266 | } |
267 | |
268 | void ento::registerSimpleStreamChecker(CheckerManager &mgr) { |
269 | mgr.registerChecker<SimpleStreamChecker>(); |
270 | } |
271 | |
272 | |
273 | bool ento::shouldRegisterSimpleStreamChecker(const LangOptions &LO) { |
274 | return true; |
275 | } |
276 | |