xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp (revision 2946cd701067404b99c39fb29dc9c74bd7193eb3)
1 //===-- StreamChecker.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 // This file defines checkers that model and check stream handling functions.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
14 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
15 #include "clang/StaticAnalyzer/Core/Checker.h"
16 #include "clang/StaticAnalyzer/Core/CheckerManager.h"
17 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
18 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
19 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
20 #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
21 
22 using namespace clang;
23 using namespace ento;
24 
25 namespace {
26 
27 struct StreamState {
28   enum Kind { Opened, Closed, OpenFailed, Escaped } K;
29   const Stmt *S;
30 
31   StreamState(Kind k, const Stmt *s) : K(k), S(s) {}
32 
33   bool isOpened() const { return K == Opened; }
34   bool isClosed() const { return K == Closed; }
35   //bool isOpenFailed() const { return K == OpenFailed; }
36   //bool isEscaped() const { return K == Escaped; }
37 
38   bool operator==(const StreamState &X) const {
39     return K == X.K && S == X.S;
40   }
41 
42   static StreamState getOpened(const Stmt *s) { return StreamState(Opened, s); }
43   static StreamState getClosed(const Stmt *s) { return StreamState(Closed, s); }
44   static StreamState getOpenFailed(const Stmt *s) {
45     return StreamState(OpenFailed, s);
46   }
47   static StreamState getEscaped(const Stmt *s) {
48     return StreamState(Escaped, s);
49   }
50 
51   void Profile(llvm::FoldingSetNodeID &ID) const {
52     ID.AddInteger(K);
53     ID.AddPointer(S);
54   }
55 };
56 
57 class StreamChecker : public Checker<eval::Call,
58                                      check::DeadSymbols > {
59   mutable IdentifierInfo *II_fopen, *II_tmpfile, *II_fclose, *II_fread,
60                  *II_fwrite,
61                  *II_fseek, *II_ftell, *II_rewind, *II_fgetpos, *II_fsetpos,
62                  *II_clearerr, *II_feof, *II_ferror, *II_fileno;
63   mutable std::unique_ptr<BuiltinBug> BT_nullfp, BT_illegalwhence,
64       BT_doubleclose, BT_ResourceLeak;
65 
66 public:
67   StreamChecker()
68     : II_fopen(nullptr), II_tmpfile(nullptr), II_fclose(nullptr),
69       II_fread(nullptr), II_fwrite(nullptr), II_fseek(nullptr),
70       II_ftell(nullptr), II_rewind(nullptr), II_fgetpos(nullptr),
71       II_fsetpos(nullptr), II_clearerr(nullptr), II_feof(nullptr),
72       II_ferror(nullptr), II_fileno(nullptr) {}
73 
74   bool evalCall(const CallExpr *CE, CheckerContext &C) const;
75   void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
76 
77 private:
78   void Fopen(CheckerContext &C, const CallExpr *CE) const;
79   void Tmpfile(CheckerContext &C, const CallExpr *CE) const;
80   void Fclose(CheckerContext &C, const CallExpr *CE) const;
81   void Fread(CheckerContext &C, const CallExpr *CE) const;
82   void Fwrite(CheckerContext &C, const CallExpr *CE) const;
83   void Fseek(CheckerContext &C, const CallExpr *CE) const;
84   void Ftell(CheckerContext &C, const CallExpr *CE) const;
85   void Rewind(CheckerContext &C, const CallExpr *CE) const;
86   void Fgetpos(CheckerContext &C, const CallExpr *CE) const;
87   void Fsetpos(CheckerContext &C, const CallExpr *CE) const;
88   void Clearerr(CheckerContext &C, const CallExpr *CE) const;
89   void Feof(CheckerContext &C, const CallExpr *CE) const;
90   void Ferror(CheckerContext &C, const CallExpr *CE) const;
91   void Fileno(CheckerContext &C, const CallExpr *CE) const;
92 
93   void OpenFileAux(CheckerContext &C, const CallExpr *CE) const;
94 
95   ProgramStateRef CheckNullStream(SVal SV, ProgramStateRef state,
96                                  CheckerContext &C) const;
97   ProgramStateRef CheckDoubleClose(const CallExpr *CE, ProgramStateRef state,
98                                  CheckerContext &C) const;
99 };
100 
101 } // end anonymous namespace
102 
103 REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
104 
105 
106 bool StreamChecker::evalCall(const CallExpr *CE, CheckerContext &C) const {
107   const FunctionDecl *FD = C.getCalleeDecl(CE);
108   if (!FD || FD->getKind() != Decl::Function)
109     return false;
110 
111   ASTContext &Ctx = C.getASTContext();
112   if (!II_fopen)
113     II_fopen = &Ctx.Idents.get("fopen");
114   if (!II_tmpfile)
115     II_tmpfile = &Ctx.Idents.get("tmpfile");
116   if (!II_fclose)
117     II_fclose = &Ctx.Idents.get("fclose");
118   if (!II_fread)
119     II_fread = &Ctx.Idents.get("fread");
120   if (!II_fwrite)
121     II_fwrite = &Ctx.Idents.get("fwrite");
122   if (!II_fseek)
123     II_fseek = &Ctx.Idents.get("fseek");
124   if (!II_ftell)
125     II_ftell = &Ctx.Idents.get("ftell");
126   if (!II_rewind)
127     II_rewind = &Ctx.Idents.get("rewind");
128   if (!II_fgetpos)
129     II_fgetpos = &Ctx.Idents.get("fgetpos");
130   if (!II_fsetpos)
131     II_fsetpos = &Ctx.Idents.get("fsetpos");
132   if (!II_clearerr)
133     II_clearerr = &Ctx.Idents.get("clearerr");
134   if (!II_feof)
135     II_feof = &Ctx.Idents.get("feof");
136   if (!II_ferror)
137     II_ferror = &Ctx.Idents.get("ferror");
138   if (!II_fileno)
139     II_fileno = &Ctx.Idents.get("fileno");
140 
141   if (FD->getIdentifier() == II_fopen) {
142     Fopen(C, CE);
143     return true;
144   }
145   if (FD->getIdentifier() == II_tmpfile) {
146     Tmpfile(C, CE);
147     return true;
148   }
149   if (FD->getIdentifier() == II_fclose) {
150     Fclose(C, CE);
151     return true;
152   }
153   if (FD->getIdentifier() == II_fread) {
154     Fread(C, CE);
155     return true;
156   }
157   if (FD->getIdentifier() == II_fwrite) {
158     Fwrite(C, CE);
159     return true;
160   }
161   if (FD->getIdentifier() == II_fseek) {
162     Fseek(C, CE);
163     return true;
164   }
165   if (FD->getIdentifier() == II_ftell) {
166     Ftell(C, CE);
167     return true;
168   }
169   if (FD->getIdentifier() == II_rewind) {
170     Rewind(C, CE);
171     return true;
172   }
173   if (FD->getIdentifier() == II_fgetpos) {
174     Fgetpos(C, CE);
175     return true;
176   }
177   if (FD->getIdentifier() == II_fsetpos) {
178     Fsetpos(C, CE);
179     return true;
180   }
181   if (FD->getIdentifier() == II_clearerr) {
182     Clearerr(C, CE);
183     return true;
184   }
185   if (FD->getIdentifier() == II_feof) {
186     Feof(C, CE);
187     return true;
188   }
189   if (FD->getIdentifier() == II_ferror) {
190     Ferror(C, CE);
191     return true;
192   }
193   if (FD->getIdentifier() == II_fileno) {
194     Fileno(C, CE);
195     return true;
196   }
197 
198   return false;
199 }
200 
201 void StreamChecker::Fopen(CheckerContext &C, const CallExpr *CE) const {
202   OpenFileAux(C, CE);
203 }
204 
205 void StreamChecker::Tmpfile(CheckerContext &C, const CallExpr *CE) const {
206   OpenFileAux(C, CE);
207 }
208 
209 void StreamChecker::OpenFileAux(CheckerContext &C, const CallExpr *CE) const {
210   ProgramStateRef state = C.getState();
211   SValBuilder &svalBuilder = C.getSValBuilder();
212   const LocationContext *LCtx = C.getPredecessor()->getLocationContext();
213   DefinedSVal RetVal = svalBuilder.conjureSymbolVal(nullptr, CE, LCtx,
214                                                     C.blockCount())
215       .castAs<DefinedSVal>();
216   state = state->BindExpr(CE, C.getLocationContext(), RetVal);
217 
218   ConstraintManager &CM = C.getConstraintManager();
219   // Bifurcate the state into two: one with a valid FILE* pointer, the other
220   // with a NULL.
221   ProgramStateRef stateNotNull, stateNull;
222   std::tie(stateNotNull, stateNull) = CM.assumeDual(state, RetVal);
223 
224   if (SymbolRef Sym = RetVal.getAsSymbol()) {
225     // if RetVal is not NULL, set the symbol's state to Opened.
226     stateNotNull =
227       stateNotNull->set<StreamMap>(Sym,StreamState::getOpened(CE));
228     stateNull =
229       stateNull->set<StreamMap>(Sym, StreamState::getOpenFailed(CE));
230 
231     C.addTransition(stateNotNull);
232     C.addTransition(stateNull);
233   }
234 }
235 
236 void StreamChecker::Fclose(CheckerContext &C, const CallExpr *CE) const {
237   ProgramStateRef state = CheckDoubleClose(CE, C.getState(), C);
238   if (state)
239     C.addTransition(state);
240 }
241 
242 void StreamChecker::Fread(CheckerContext &C, const CallExpr *CE) const {
243   ProgramStateRef state = C.getState();
244   if (!CheckNullStream(C.getSVal(CE->getArg(3)), state, C))
245     return;
246 }
247 
248 void StreamChecker::Fwrite(CheckerContext &C, const CallExpr *CE) const {
249   ProgramStateRef state = C.getState();
250   if (!CheckNullStream(C.getSVal(CE->getArg(3)), state, C))
251     return;
252 }
253 
254 void StreamChecker::Fseek(CheckerContext &C, const CallExpr *CE) const {
255   ProgramStateRef state = C.getState();
256   if (!(state = CheckNullStream(C.getSVal(CE->getArg(0)), state, C)))
257     return;
258   // Check the legality of the 'whence' argument of 'fseek'.
259   SVal Whence = state->getSVal(CE->getArg(2), C.getLocationContext());
260   Optional<nonloc::ConcreteInt> CI = Whence.getAs<nonloc::ConcreteInt>();
261 
262   if (!CI)
263     return;
264 
265   int64_t x = CI->getValue().getSExtValue();
266   if (x >= 0 && x <= 2)
267     return;
268 
269   if (ExplodedNode *N = C.generateNonFatalErrorNode(state)) {
270     if (!BT_illegalwhence)
271       BT_illegalwhence.reset(
272           new BuiltinBug(this, "Illegal whence argument",
273                          "The whence argument to fseek() should be "
274                          "SEEK_SET, SEEK_END, or SEEK_CUR."));
275     C.emitReport(llvm::make_unique<BugReport>(
276         *BT_illegalwhence, BT_illegalwhence->getDescription(), N));
277   }
278 }
279 
280 void StreamChecker::Ftell(CheckerContext &C, const CallExpr *CE) const {
281   ProgramStateRef state = C.getState();
282   if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C))
283     return;
284 }
285 
286 void StreamChecker::Rewind(CheckerContext &C, const CallExpr *CE) const {
287   ProgramStateRef state = C.getState();
288   if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C))
289     return;
290 }
291 
292 void StreamChecker::Fgetpos(CheckerContext &C, const CallExpr *CE) const {
293   ProgramStateRef state = C.getState();
294   if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C))
295     return;
296 }
297 
298 void StreamChecker::Fsetpos(CheckerContext &C, const CallExpr *CE) const {
299   ProgramStateRef state = C.getState();
300   if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C))
301     return;
302 }
303 
304 void StreamChecker::Clearerr(CheckerContext &C, const CallExpr *CE) const {
305   ProgramStateRef state = C.getState();
306   if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C))
307     return;
308 }
309 
310 void StreamChecker::Feof(CheckerContext &C, const CallExpr *CE) const {
311   ProgramStateRef state = C.getState();
312   if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C))
313     return;
314 }
315 
316 void StreamChecker::Ferror(CheckerContext &C, const CallExpr *CE) const {
317   ProgramStateRef state = C.getState();
318   if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C))
319     return;
320 }
321 
322 void StreamChecker::Fileno(CheckerContext &C, const CallExpr *CE) const {
323   ProgramStateRef state = C.getState();
324   if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C))
325     return;
326 }
327 
328 ProgramStateRef StreamChecker::CheckNullStream(SVal SV, ProgramStateRef state,
329                                     CheckerContext &C) const {
330   Optional<DefinedSVal> DV = SV.getAs<DefinedSVal>();
331   if (!DV)
332     return nullptr;
333 
334   ConstraintManager &CM = C.getConstraintManager();
335   ProgramStateRef stateNotNull, stateNull;
336   std::tie(stateNotNull, stateNull) = CM.assumeDual(state, *DV);
337 
338   if (!stateNotNull && stateNull) {
339     if (ExplodedNode *N = C.generateErrorNode(stateNull)) {
340       if (!BT_nullfp)
341         BT_nullfp.reset(new BuiltinBug(this, "NULL stream pointer",
342                                        "Stream pointer might be NULL."));
343       C.emitReport(llvm::make_unique<BugReport>(
344           *BT_nullfp, BT_nullfp->getDescription(), N));
345     }
346     return nullptr;
347   }
348   return stateNotNull;
349 }
350 
351 ProgramStateRef StreamChecker::CheckDoubleClose(const CallExpr *CE,
352                                                ProgramStateRef state,
353                                                CheckerContext &C) const {
354   SymbolRef Sym = C.getSVal(CE->getArg(0)).getAsSymbol();
355   if (!Sym)
356     return state;
357 
358   const StreamState *SS = state->get<StreamMap>(Sym);
359 
360   // If the file stream is not tracked, return.
361   if (!SS)
362     return state;
363 
364   // Check: Double close a File Descriptor could cause undefined behaviour.
365   // Conforming to man-pages
366   if (SS->isClosed()) {
367     ExplodedNode *N = C.generateErrorNode();
368     if (N) {
369       if (!BT_doubleclose)
370         BT_doubleclose.reset(new BuiltinBug(
371             this, "Double fclose", "Try to close a file Descriptor already"
372                                    " closed. Cause undefined behaviour."));
373       C.emitReport(llvm::make_unique<BugReport>(
374           *BT_doubleclose, BT_doubleclose->getDescription(), N));
375     }
376     return nullptr;
377   }
378 
379   // Close the File Descriptor.
380   return state->set<StreamMap>(Sym, StreamState::getClosed(CE));
381 }
382 
383 void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
384                                      CheckerContext &C) const {
385   ProgramStateRef state = C.getState();
386 
387   // TODO: Clean up the state.
388   const StreamMapTy &Map = state->get<StreamMap>();
389   for (const auto &I: Map) {
390     SymbolRef Sym = I.first;
391     const StreamState &SS = I.second;
392     if (!SymReaper.isDead(Sym) || !SS.isOpened())
393       continue;
394 
395     ExplodedNode *N = C.generateErrorNode();
396     if (!N)
397       return;
398 
399     if (!BT_ResourceLeak)
400       BT_ResourceLeak.reset(
401           new BuiltinBug(this, "Resource Leak",
402                          "Opened File never closed. Potential Resource leak."));
403     C.emitReport(llvm::make_unique<BugReport>(
404         *BT_ResourceLeak, BT_ResourceLeak->getDescription(), N));
405   }
406 }
407 
408 void ento::registerStreamChecker(CheckerManager &mgr) {
409   mgr.registerChecker<StreamChecker>();
410 }
411