Clang Project

clang_source_code/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
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
22using namespace clang;
23using namespace ento;
24
25namespace {
26
27struct StreamState {
28  enum Kind { OpenedClosedOpenFailedEscaped } K;
29  const Stmt *S;
30
31  StreamState(Kind kconst 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 &Xconst {
39    return K == X.K && S == X.S;
40  }
41
42  static StreamState getOpened(const Stmt *s) { return StreamState(Openeds); }
43  static StreamState getClosed(const Stmt *s) { return StreamState(Closeds); }
44  static StreamState getOpenFailed(const Stmt *s) {
45    return StreamState(OpenFaileds);
46  }
47  static StreamState getEscaped(const Stmt *s) {
48    return StreamState(Escapeds);
49  }
50
51  void Profile(llvm::FoldingSetNodeID &IDconst {
52    ID.AddInteger(K);
53    ID.AddPointer(S);
54  }
55};
56
57class 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<BuiltinBugBT_nullfpBT_illegalwhence,
64      BT_doublecloseBT_ResourceLeak;
65
66public:
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 *CECheckerContext &Cconst;
75  void checkDeadSymbols(SymbolReaper &SymReaperCheckerContext &Cconst;
76
77private:
78  void Fopen(CheckerContext &Cconst CallExpr *CEconst;
79  void Tmpfile(CheckerContext &Cconst CallExpr *CEconst;
80  void Fclose(CheckerContext &Cconst CallExpr *CEconst;
81  void Fread(CheckerContext &Cconst CallExpr *CEconst;
82  void Fwrite(CheckerContext &Cconst CallExpr *CEconst;
83  void Fseek(CheckerContext &Cconst CallExpr *CEconst;
84  void Ftell(CheckerContext &Cconst CallExpr *CEconst;
85  void Rewind(CheckerContext &Cconst CallExpr *CEconst;
86  void Fgetpos(CheckerContext &Cconst CallExpr *CEconst;
87  void Fsetpos(CheckerContext &Cconst CallExpr *CEconst;
88  void Clearerr(CheckerContext &Cconst CallExpr *CEconst;
89  void Feof(CheckerContext &Cconst CallExpr *CEconst;
90  void Ferror(CheckerContext &Cconst CallExpr *CEconst;
91  void Fileno(CheckerContext &Cconst CallExpr *CEconst;
92
93  void OpenFileAux(CheckerContext &Cconst CallExpr *CEconst;
94
95  ProgramStateRef CheckNullStream(SVal SVProgramStateRef state,
96                                 CheckerContext &Cconst;
97  ProgramStateRef CheckDoubleClose(const CallExpr *CEProgramStateRef state,
98                                 CheckerContext &Cconst;
99};
100
101// end anonymous namespace
102
103REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
104
105
106bool StreamChecker::evalCall(const CallExpr *CECheckerContext &Cconst {
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(CCE);
143    return true;
144  }
145  if (FD->getIdentifier() == II_tmpfile) {
146    Tmpfile(CCE);
147    return true;
148  }
149  if (FD->getIdentifier() == II_fclose) {
150    Fclose(CCE);
151    return true;
152  }
153  if (FD->getIdentifier() == II_fread) {
154    Fread(CCE);
155    return true;
156  }
157  if (FD->getIdentifier() == II_fwrite) {
158    Fwrite(CCE);
159    return true;
160  }
161  if (FD->getIdentifier() == II_fseek) {
162    Fseek(CCE);
163    return true;
164  }
165  if (FD->getIdentifier() == II_ftell) {
166    Ftell(CCE);
167    return true;
168  }
169  if (FD->getIdentifier() == II_rewind) {
170    Rewind(CCE);
171    return true;
172  }
173  if (FD->getIdentifier() == II_fgetpos) {
174    Fgetpos(CCE);
175    return true;
176  }
177  if (FD->getIdentifier() == II_fsetpos) {
178    Fsetpos(CCE);
179    return true;
180  }
181  if (FD->getIdentifier() == II_clearerr) {
182    Clearerr(CCE);
183    return true;
184  }
185  if (FD->getIdentifier() == II_feof) {
186    Feof(CCE);
187    return true;
188  }
189  if (FD->getIdentifier() == II_ferror) {
190    Ferror(CCE);
191    return true;
192  }
193  if (FD->getIdentifier() == II_fileno) {
194    Fileno(CCE);
195    return true;
196  }
197
198  return false;
199}
200
201void StreamChecker::Fopen(CheckerContext &Cconst CallExpr *CEconst {
202  OpenFileAux(CCE);
203}
204
205void StreamChecker::Tmpfile(CheckerContext &Cconst CallExpr *CEconst {
206  OpenFileAux(CCE);
207}
208
209void StreamChecker::OpenFileAux(CheckerContext &Cconst CallExpr *CEconst {
210  ProgramStateRef state = C.getState();
211  SValBuilder &svalBuilder = C.getSValBuilder();
212  const LocationContext *LCtx = C.getPredecessor()->getLocationContext();
213  DefinedSVal RetVal = svalBuilder.conjureSymbolVal(nullptrCELCtx,
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 stateNotNullstateNull;
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
236void StreamChecker::Fclose(CheckerContext &Cconst CallExpr *CEconst {
237  ProgramStateRef state = CheckDoubleClose(CE, C.getState(), C);
238  if (state)
239    C.addTransition(state);
240}
241
242void StreamChecker::Fread(CheckerContext &Cconst CallExpr *CEconst {
243  ProgramStateRef state = C.getState();
244  if (!CheckNullStream(C.getSVal(CE->getArg(3)), state, C))
245    return;
246}
247
248void StreamChecker::Fwrite(CheckerContext &Cconst CallExpr *CEconst {
249  ProgramStateRef state = C.getState();
250  if (!CheckNullStream(C.getSVal(CE->getArg(3)), state, C))
251    return;
252}
253
254void StreamChecker::Fseek(CheckerContext &Cconst CallExpr *CEconst {
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::ConcreteIntCI = 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
280void StreamChecker::Ftell(CheckerContext &Cconst CallExpr *CEconst {
281  ProgramStateRef state = C.getState();
282  if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C))
283    return;
284}
285
286void StreamChecker::Rewind(CheckerContext &Cconst CallExpr *CEconst {
287  ProgramStateRef state = C.getState();
288  if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C))
289    return;
290}
291
292void StreamChecker::Fgetpos(CheckerContext &Cconst CallExpr *CEconst {
293  ProgramStateRef state = C.getState();
294  if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C))
295    return;
296}
297
298void StreamChecker::Fsetpos(CheckerContext &Cconst CallExpr *CEconst {
299  ProgramStateRef state = C.getState();
300  if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C))
301    return;
302}
303
304void StreamChecker::Clearerr(CheckerContext &Cconst CallExpr *CEconst {
305  ProgramStateRef state = C.getState();
306  if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C))
307    return;
308}
309
310void StreamChecker::Feof(CheckerContext &Cconst CallExpr *CEconst {
311  ProgramStateRef state = C.getState();
312  if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C))
313    return;
314}
315
316void StreamChecker::Ferror(CheckerContext &Cconst CallExpr *CEconst {
317  ProgramStateRef state = C.getState();
318  if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C))
319    return;
320}
321
322void StreamChecker::Fileno(CheckerContext &Cconst CallExpr *CEconst {
323  ProgramStateRef state = C.getState();
324  if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C))
325    return;
326}
327
328ProgramStateRef StreamChecker::CheckNullStream(SVal SVProgramStateRef state,
329                                    CheckerContext &Cconst {
330  Optional<DefinedSValDV = SV.getAs<DefinedSVal>();
331  if (!DV)
332    return nullptr;
333
334  ConstraintManager &CM = C.getConstraintManager();
335  ProgramStateRef stateNotNullstateNull;
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
351ProgramStateRef StreamChecker::CheckDoubleClose(const CallExpr *CE,
352                                               ProgramStateRef state,
353                                               CheckerContext &Cconst {
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
383void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
384                                     CheckerContext &Cconst {
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
408void ento::registerStreamChecker(CheckerManager &mgr) {
409  mgr.registerChecker<StreamChecker>();
410}
411
412bool ento::shouldRegisterStreamChecker(const LangOptions &LO) {
413  return true;
414}
415