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