xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp (revision d99bd55a5e092774214ba31fc5a871bfc31e711c)
1 //===-- StreamChecker.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 // This file defines checkers that model and check stream handling functions.
11 //
12 //===----------------------------------------------------------------------===//
13 
14 #include "ExprEngineExperimentalChecks.h"
15 #include "clang/StaticAnalyzer/BugReporter/BugType.h"
16 #include "clang/StaticAnalyzer/PathSensitive/CheckerVisitor.h"
17 #include "clang/StaticAnalyzer/PathSensitive/GRState.h"
18 #include "clang/StaticAnalyzer/PathSensitive/GRStateTrait.h"
19 #include "clang/StaticAnalyzer/PathSensitive/SymbolManager.h"
20 #include "llvm/ADT/ImmutableMap.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 CheckerVisitor<StreamChecker> {
58   IdentifierInfo *II_fopen, *II_tmpfile, *II_fclose, *II_fread, *II_fwrite,
59                  *II_fseek, *II_ftell, *II_rewind, *II_fgetpos, *II_fsetpos,
60                  *II_clearerr, *II_feof, *II_ferror, *II_fileno;
61   BuiltinBug *BT_nullfp, *BT_illegalwhence, *BT_doubleclose, *BT_ResourceLeak;
62 
63 public:
64   StreamChecker()
65     : II_fopen(0), II_tmpfile(0) ,II_fclose(0), II_fread(0), II_fwrite(0),
66       II_fseek(0), II_ftell(0), II_rewind(0), II_fgetpos(0), II_fsetpos(0),
67       II_clearerr(0), II_feof(0), II_ferror(0), II_fileno(0),
68       BT_nullfp(0), BT_illegalwhence(0), BT_doubleclose(0),
69       BT_ResourceLeak(0) {}
70 
71   static void *getTag() {
72     static int x;
73     return &x;
74   }
75 
76   virtual bool evalCallExpr(CheckerContext &C, const CallExpr *CE);
77   void evalDeadSymbols(CheckerContext &C, SymbolReaper &SymReaper);
78   void evalEndPath(EndPathNodeBuilder &B, void *tag, ExprEngine &Eng);
79   void PreVisitReturnStmt(CheckerContext &C, const ReturnStmt *S);
80 
81 private:
82   void Fopen(CheckerContext &C, const CallExpr *CE);
83   void Tmpfile(CheckerContext &C, const CallExpr *CE);
84   void Fclose(CheckerContext &C, const CallExpr *CE);
85   void Fread(CheckerContext &C, const CallExpr *CE);
86   void Fwrite(CheckerContext &C, const CallExpr *CE);
87   void Fseek(CheckerContext &C, const CallExpr *CE);
88   void Ftell(CheckerContext &C, const CallExpr *CE);
89   void Rewind(CheckerContext &C, const CallExpr *CE);
90   void Fgetpos(CheckerContext &C, const CallExpr *CE);
91   void Fsetpos(CheckerContext &C, const CallExpr *CE);
92   void Clearerr(CheckerContext &C, const CallExpr *CE);
93   void Feof(CheckerContext &C, const CallExpr *CE);
94   void Ferror(CheckerContext &C, const CallExpr *CE);
95   void Fileno(CheckerContext &C, const CallExpr *CE);
96 
97   void OpenFileAux(CheckerContext &C, const CallExpr *CE);
98 
99   const GRState *CheckNullStream(SVal SV, const GRState *state,
100                                  CheckerContext &C);
101   const GRState *CheckDoubleClose(const CallExpr *CE, const GRState *state,
102                                  CheckerContext &C);
103 };
104 
105 } // end anonymous namespace
106 
107 namespace clang {
108 namespace ento {
109   template <>
110   struct GRStateTrait<StreamState>
111     : public GRStatePartialTrait<llvm::ImmutableMap<SymbolRef, StreamState> > {
112     static void *GDMIndex() { return StreamChecker::getTag(); }
113   };
114 }
115 }
116 
117 void ento::RegisterStreamChecker(ExprEngine &Eng) {
118   Eng.registerCheck(new StreamChecker());
119 }
120 
121 bool StreamChecker::evalCallExpr(CheckerContext &C, const CallExpr *CE) {
122   const GRState *state = C.getState();
123   const Expr *Callee = CE->getCallee();
124   SVal L = state->getSVal(Callee);
125   const FunctionDecl *FD = L.getAsFunctionDecl();
126   if (!FD)
127     return false;
128 
129   ASTContext &Ctx = C.getASTContext();
130   if (!II_fopen)
131     II_fopen = &Ctx.Idents.get("fopen");
132   if (!II_tmpfile)
133     II_tmpfile = &Ctx.Idents.get("tmpfile");
134   if (!II_fclose)
135     II_fclose = &Ctx.Idents.get("fclose");
136   if (!II_fread)
137     II_fread = &Ctx.Idents.get("fread");
138   if (!II_fwrite)
139     II_fwrite = &Ctx.Idents.get("fwrite");
140   if (!II_fseek)
141     II_fseek = &Ctx.Idents.get("fseek");
142   if (!II_ftell)
143     II_ftell = &Ctx.Idents.get("ftell");
144   if (!II_rewind)
145     II_rewind = &Ctx.Idents.get("rewind");
146   if (!II_fgetpos)
147     II_fgetpos = &Ctx.Idents.get("fgetpos");
148   if (!II_fsetpos)
149     II_fsetpos = &Ctx.Idents.get("fsetpos");
150   if (!II_clearerr)
151     II_clearerr = &Ctx.Idents.get("clearerr");
152   if (!II_feof)
153     II_feof = &Ctx.Idents.get("feof");
154   if (!II_ferror)
155     II_ferror = &Ctx.Idents.get("ferror");
156   if (!II_fileno)
157     II_fileno = &Ctx.Idents.get("fileno");
158 
159   if (FD->getIdentifier() == II_fopen) {
160     Fopen(C, CE);
161     return true;
162   }
163   if (FD->getIdentifier() == II_tmpfile) {
164     Tmpfile(C, CE);
165     return true;
166   }
167   if (FD->getIdentifier() == II_fclose) {
168     Fclose(C, CE);
169     return true;
170   }
171   if (FD->getIdentifier() == II_fread) {
172     Fread(C, CE);
173     return true;
174   }
175   if (FD->getIdentifier() == II_fwrite) {
176     Fwrite(C, CE);
177     return true;
178   }
179   if (FD->getIdentifier() == II_fseek) {
180     Fseek(C, CE);
181     return true;
182   }
183   if (FD->getIdentifier() == II_ftell) {
184     Ftell(C, CE);
185     return true;
186   }
187   if (FD->getIdentifier() == II_rewind) {
188     Rewind(C, CE);
189     return true;
190   }
191   if (FD->getIdentifier() == II_fgetpos) {
192     Fgetpos(C, CE);
193     return true;
194   }
195   if (FD->getIdentifier() == II_fsetpos) {
196     Fsetpos(C, CE);
197     return true;
198   }
199   if (FD->getIdentifier() == II_clearerr) {
200     Clearerr(C, CE);
201     return true;
202   }
203   if (FD->getIdentifier() == II_feof) {
204     Feof(C, CE);
205     return true;
206   }
207   if (FD->getIdentifier() == II_ferror) {
208     Ferror(C, CE);
209     return true;
210   }
211   if (FD->getIdentifier() == II_fileno) {
212     Fileno(C, CE);
213     return true;
214   }
215 
216   return false;
217 }
218 
219 void StreamChecker::Fopen(CheckerContext &C, const CallExpr *CE) {
220   OpenFileAux(C, CE);
221 }
222 
223 void StreamChecker::Tmpfile(CheckerContext &C, const CallExpr *CE) {
224   OpenFileAux(C, CE);
225 }
226 
227 void StreamChecker::OpenFileAux(CheckerContext &C, const CallExpr *CE) {
228   const GRState *state = C.getState();
229   unsigned Count = C.getNodeBuilder().getCurrentBlockCount();
230   SValBuilder &svalBuilder = C.getSValBuilder();
231   DefinedSVal RetVal =
232     cast<DefinedSVal>(svalBuilder.getConjuredSymbolVal(0, CE, Count));
233   state = state->BindExpr(CE, RetVal);
234 
235   ConstraintManager &CM = C.getConstraintManager();
236   // Bifurcate the state into two: one with a valid FILE* pointer, the other
237   // with a NULL.
238   const GRState *stateNotNull, *stateNull;
239   llvm::tie(stateNotNull, stateNull) = CM.assumeDual(state, RetVal);
240 
241   if (SymbolRef Sym = RetVal.getAsSymbol()) {
242     // if RetVal is not NULL, set the symbol's state to Opened.
243     stateNotNull =
244       stateNotNull->set<StreamState>(Sym,StreamState::getOpened(CE));
245     stateNull =
246       stateNull->set<StreamState>(Sym, StreamState::getOpenFailed(CE));
247 
248     C.addTransition(stateNotNull);
249     C.addTransition(stateNull);
250   }
251 }
252 
253 void StreamChecker::Fclose(CheckerContext &C, const CallExpr *CE) {
254   const GRState *state = CheckDoubleClose(CE, C.getState(), C);
255   if (state)
256     C.addTransition(state);
257 }
258 
259 void StreamChecker::Fread(CheckerContext &C, const CallExpr *CE) {
260   const GRState *state = C.getState();
261   if (!CheckNullStream(state->getSVal(CE->getArg(3)), state, C))
262     return;
263 }
264 
265 void StreamChecker::Fwrite(CheckerContext &C, const CallExpr *CE) {
266   const GRState *state = C.getState();
267   if (!CheckNullStream(state->getSVal(CE->getArg(3)), state, C))
268     return;
269 }
270 
271 void StreamChecker::Fseek(CheckerContext &C, const CallExpr *CE) {
272   const GRState *state = C.getState();
273   if (!(state = CheckNullStream(state->getSVal(CE->getArg(0)), state, C)))
274     return;
275   // Check the legality of the 'whence' argument of 'fseek'.
276   SVal Whence = state->getSVal(CE->getArg(2));
277   const nonloc::ConcreteInt *CI = dyn_cast<nonloc::ConcreteInt>(&Whence);
278 
279   if (!CI)
280     return;
281 
282   int64_t x = CI->getValue().getSExtValue();
283   if (x >= 0 && x <= 2)
284     return;
285 
286   if (ExplodedNode *N = C.generateNode(state)) {
287     if (!BT_illegalwhence)
288       BT_illegalwhence = new BuiltinBug("Illegal whence argument",
289 					"The whence argument to fseek() should be "
290 					"SEEK_SET, SEEK_END, or SEEK_CUR.");
291     BugReport *R = new BugReport(*BT_illegalwhence,
292 				 BT_illegalwhence->getDescription(), N);
293     C.EmitReport(R);
294   }
295 }
296 
297 void StreamChecker::Ftell(CheckerContext &C, const CallExpr *CE) {
298   const GRState *state = C.getState();
299   if (!CheckNullStream(state->getSVal(CE->getArg(0)), state, C))
300     return;
301 }
302 
303 void StreamChecker::Rewind(CheckerContext &C, const CallExpr *CE) {
304   const GRState *state = C.getState();
305   if (!CheckNullStream(state->getSVal(CE->getArg(0)), state, C))
306     return;
307 }
308 
309 void StreamChecker::Fgetpos(CheckerContext &C, const CallExpr *CE) {
310   const GRState *state = C.getState();
311   if (!CheckNullStream(state->getSVal(CE->getArg(0)), state, C))
312     return;
313 }
314 
315 void StreamChecker::Fsetpos(CheckerContext &C, const CallExpr *CE) {
316   const GRState *state = C.getState();
317   if (!CheckNullStream(state->getSVal(CE->getArg(0)), state, C))
318     return;
319 }
320 
321 void StreamChecker::Clearerr(CheckerContext &C, const CallExpr *CE) {
322   const GRState *state = C.getState();
323   if (!CheckNullStream(state->getSVal(CE->getArg(0)), state, C))
324     return;
325 }
326 
327 void StreamChecker::Feof(CheckerContext &C, const CallExpr *CE) {
328   const GRState *state = C.getState();
329   if (!CheckNullStream(state->getSVal(CE->getArg(0)), state, C))
330     return;
331 }
332 
333 void StreamChecker::Ferror(CheckerContext &C, const CallExpr *CE) {
334   const GRState *state = C.getState();
335   if (!CheckNullStream(state->getSVal(CE->getArg(0)), state, C))
336     return;
337 }
338 
339 void StreamChecker::Fileno(CheckerContext &C, const CallExpr *CE) {
340   const GRState *state = C.getState();
341   if (!CheckNullStream(state->getSVal(CE->getArg(0)), state, C))
342     return;
343 }
344 
345 const GRState *StreamChecker::CheckNullStream(SVal SV, const GRState *state,
346                                     CheckerContext &C) {
347   const DefinedSVal *DV = dyn_cast<DefinedSVal>(&SV);
348   if (!DV)
349     return 0;
350 
351   ConstraintManager &CM = C.getConstraintManager();
352   const GRState *stateNotNull, *stateNull;
353   llvm::tie(stateNotNull, stateNull) = CM.assumeDual(state, *DV);
354 
355   if (!stateNotNull && stateNull) {
356     if (ExplodedNode *N = C.generateSink(stateNull)) {
357       if (!BT_nullfp)
358         BT_nullfp = new BuiltinBug("NULL stream pointer",
359                                      "Stream pointer might be NULL.");
360       BugReport *R =new BugReport(*BT_nullfp, BT_nullfp->getDescription(), N);
361       C.EmitReport(R);
362     }
363     return 0;
364   }
365   return stateNotNull;
366 }
367 
368 const GRState *StreamChecker::CheckDoubleClose(const CallExpr *CE,
369                                                const GRState *state,
370                                                CheckerContext &C) {
371   SymbolRef Sym = state->getSVal(CE->getArg(0)).getAsSymbol();
372   if (!Sym)
373     return state;
374 
375   const StreamState *SS = state->get<StreamState>(Sym);
376 
377   // If the file stream is not tracked, return.
378   if (!SS)
379     return state;
380 
381   // Check: Double close a File Descriptor could cause undefined behaviour.
382   // Conforming to man-pages
383   if (SS->isClosed()) {
384     ExplodedNode *N = C.generateSink();
385     if (N) {
386       if (!BT_doubleclose)
387         BT_doubleclose = new BuiltinBug("Double fclose",
388                                         "Try to close a file Descriptor already"
389                                         " closed. Cause undefined behaviour.");
390       BugReport *R = new BugReport(*BT_doubleclose,
391                                    BT_doubleclose->getDescription(), N);
392       C.EmitReport(R);
393     }
394     return NULL;
395   }
396 
397   // Close the File Descriptor.
398   return state->set<StreamState>(Sym, StreamState::getClosed(CE));
399 }
400 
401 void StreamChecker::evalDeadSymbols(CheckerContext &C,SymbolReaper &SymReaper) {
402   for (SymbolReaper::dead_iterator I = SymReaper.dead_begin(),
403          E = SymReaper.dead_end(); I != E; ++I) {
404     SymbolRef Sym = *I;
405     const GRState *state = C.getState();
406     const StreamState *SS = state->get<StreamState>(Sym);
407     if (!SS)
408       return;
409 
410     if (SS->isOpened()) {
411       ExplodedNode *N = C.generateSink();
412       if (N) {
413         if (!BT_ResourceLeak)
414           BT_ResourceLeak = new BuiltinBug("Resource Leak",
415                           "Opened File never closed. Potential Resource leak.");
416         BugReport *R = new BugReport(*BT_ResourceLeak,
417                                      BT_ResourceLeak->getDescription(), N);
418         C.EmitReport(R);
419       }
420     }
421   }
422 }
423 
424 void StreamChecker::evalEndPath(EndPathNodeBuilder &B, void *tag,
425                                 ExprEngine &Eng) {
426   SaveAndRestore<bool> OldHasGen(B.HasGeneratedNode);
427   const GRState *state = B.getState();
428   typedef llvm::ImmutableMap<SymbolRef, StreamState> SymMap;
429   SymMap M = state->get<StreamState>();
430 
431   for (SymMap::iterator I = M.begin(), E = M.end(); I != E; ++I) {
432     StreamState SS = I->second;
433     if (SS.isOpened()) {
434       ExplodedNode *N = B.generateNode(state, tag, B.getPredecessor());
435       if (N) {
436         if (!BT_ResourceLeak)
437           BT_ResourceLeak = new BuiltinBug("Resource Leak",
438                           "Opened File never closed. Potential Resource leak.");
439         BugReport *R = new BugReport(*BT_ResourceLeak,
440                                      BT_ResourceLeak->getDescription(), N);
441         Eng.getBugReporter().EmitReport(R);
442       }
443     }
444   }
445 }
446 
447 void StreamChecker::PreVisitReturnStmt(CheckerContext &C, const ReturnStmt *S) {
448   const Expr *RetE = S->getRetValue();
449   if (!RetE)
450     return;
451 
452   const GRState *state = C.getState();
453   SymbolRef Sym = state->getSVal(RetE).getAsSymbol();
454 
455   if (!Sym)
456     return;
457 
458   const StreamState *SS = state->get<StreamState>(Sym);
459   if(!SS)
460     return;
461 
462   if (SS->isOpened())
463     state = state->set<StreamState>(Sym, StreamState::getEscaped(S));
464 
465   C.addTransition(state);
466 }
467