clang  9.0.0
ValistChecker.cpp
Go to the documentation of this file.
1 //== ValistChecker.cpp - stdarg.h macro usage checker -----------*- 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 defines checkers which detect usage of uninitialized va_list values
10 // and va_start calls with no matching va_end.
11 //
12 //===----------------------------------------------------------------------===//
13 
20 
21 using namespace clang;
22 using namespace ento;
23 
24 REGISTER_SET_WITH_PROGRAMSTATE(InitializedVALists, const MemRegion *)
25 
26 namespace {
27 typedef SmallVector<const MemRegion *, 2> RegionVector;
28 
29 class ValistChecker : public Checker<check::PreCall, check::PreStmt<VAArgExpr>,
30  check::DeadSymbols> {
31  mutable std::unique_ptr<BugType> BT_leakedvalist, BT_uninitaccess;
32 
33  struct VAListAccepter {
34  CallDescription Func;
35  int VAListPos;
36  };
37  static const SmallVector<VAListAccepter, 15> VAListAccepters;
38  static const CallDescription VaStart, VaEnd, VaCopy;
39 
40 public:
41  enum CheckKind {
42  CK_Uninitialized,
43  CK_Unterminated,
44  CK_CopyToSelf,
45  CK_NumCheckKinds
46  };
47 
48  DefaultBool ChecksEnabled[CK_NumCheckKinds];
49  CheckName CheckNames[CK_NumCheckKinds];
50 
51  void checkPreStmt(const VAArgExpr *VAA, CheckerContext &C) const;
52  void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
53  void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const;
54 
55 private:
56  const MemRegion *getVAListAsRegion(SVal SV, const Expr *VAExpr,
57  bool &IsSymbolic, CheckerContext &C) const;
58  const ExplodedNode *getStartCallSite(const ExplodedNode *N,
59  const MemRegion *Reg) const;
60 
61  void reportUninitializedAccess(const MemRegion *VAList, StringRef Msg,
62  CheckerContext &C) const;
63  void reportLeakedVALists(const RegionVector &LeakedVALists, StringRef Msg1,
64  StringRef Msg2, CheckerContext &C, ExplodedNode *N,
65  bool ReportUninit = false) const;
66 
67  void checkVAListStartCall(const CallEvent &Call, CheckerContext &C,
68  bool IsCopy) const;
69  void checkVAListEndCall(const CallEvent &Call, CheckerContext &C) const;
70 
71  class ValistBugVisitor : public BugReporterVisitor {
72  public:
73  ValistBugVisitor(const MemRegion *Reg, bool IsLeak = false)
74  : Reg(Reg), IsLeak(IsLeak) {}
75  void Profile(llvm::FoldingSetNodeID &ID) const override {
76  static int X = 0;
77  ID.AddPointer(&X);
78  ID.AddPointer(Reg);
79  }
80  std::shared_ptr<PathDiagnosticPiece>
81  getEndPath(BugReporterContext &BRC, const ExplodedNode *EndPathNode,
82  BugReport &BR) override {
83  if (!IsLeak)
84  return nullptr;
85 
86  PathDiagnosticLocation L = PathDiagnosticLocation::createEndOfPath(
87  EndPathNode, BRC.getSourceManager());
88  // Do not add the statement itself as a range in case of leak.
89  return std::make_shared<PathDiagnosticEventPiece>(L, BR.getDescription(), false);
90  }
91  std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N,
92  BugReporterContext &BRC,
93  BugReport &BR) override;
94 
95  private:
96  const MemRegion *Reg;
97  bool IsLeak;
98  };
99 };
100 
102  ValistChecker::VAListAccepters = {
103  {{"vfprintf", 3}, 2},
104  {{"vfscanf", 3}, 2},
105  {{"vprintf", 2}, 1},
106  {{"vscanf", 2}, 1},
107  {{"vsnprintf", 4}, 3},
108  {{"vsprintf", 3}, 2},
109  {{"vsscanf", 3}, 2},
110  {{"vfwprintf", 3}, 2},
111  {{"vfwscanf", 3}, 2},
112  {{"vwprintf", 2}, 1},
113  {{"vwscanf", 2}, 1},
114  {{"vswprintf", 4}, 3},
115  // vswprintf is the wide version of vsnprintf,
116  // vsprintf has no wide version
117  {{"vswscanf", 3}, 2}};
118 const CallDescription ValistChecker::VaStart("__builtin_va_start", 2),
119  ValistChecker::VaCopy("__builtin_va_copy", 2),
120  ValistChecker::VaEnd("__builtin_va_end", 1);
121 } // end anonymous namespace
122 
123 void ValistChecker::checkPreCall(const CallEvent &Call,
124  CheckerContext &C) const {
125  if (!Call.isGlobalCFunction())
126  return;
127  if (Call.isCalled(VaStart))
128  checkVAListStartCall(Call, C, false);
129  else if (Call.isCalled(VaCopy))
130  checkVAListStartCall(Call, C, true);
131  else if (Call.isCalled(VaEnd))
132  checkVAListEndCall(Call, C);
133  else {
134  for (auto FuncInfo : VAListAccepters) {
135  if (!Call.isCalled(FuncInfo.Func))
136  continue;
137  bool Symbolic;
138  const MemRegion *VAList =
139  getVAListAsRegion(Call.getArgSVal(FuncInfo.VAListPos),
140  Call.getArgExpr(FuncInfo.VAListPos), Symbolic, C);
141  if (!VAList)
142  return;
143 
144  if (C.getState()->contains<InitializedVALists>(VAList))
145  return;
146 
147  // We did not see va_start call, but the source of the region is unknown.
148  // Be conservative and assume the best.
149  if (Symbolic)
150  return;
151 
152  SmallString<80> Errmsg("Function '");
153  Errmsg += FuncInfo.Func.getFunctionName();
154  Errmsg += "' is called with an uninitialized va_list argument";
155  reportUninitializedAccess(VAList, Errmsg.c_str(), C);
156  break;
157  }
158  }
159 }
160 
161 const MemRegion *ValistChecker::getVAListAsRegion(SVal SV, const Expr *E,
162  bool &IsSymbolic,
163  CheckerContext &C) const {
164  const MemRegion *Reg = SV.getAsRegion();
165  if (!Reg)
166  return nullptr;
167  // TODO: In the future this should be abstracted away by the analyzer.
168  bool VaListModelledAsArray = false;
169  if (const auto *Cast = dyn_cast<CastExpr>(E)) {
170  QualType Ty = Cast->getType();
171  VaListModelledAsArray =
172  Ty->isPointerType() && Ty->getPointeeType()->isRecordType();
173  }
174  if (const auto *DeclReg = Reg->getAs<DeclRegion>()) {
175  if (isa<ParmVarDecl>(DeclReg->getDecl()))
176  Reg = C.getState()->getSVal(SV.castAs<Loc>()).getAsRegion();
177  }
178  IsSymbolic = Reg && Reg->getAs<SymbolicRegion>();
179  // Some VarRegion based VA lists reach here as ElementRegions.
180  const auto *EReg = dyn_cast_or_null<ElementRegion>(Reg);
181  return (EReg && VaListModelledAsArray) ? EReg->getSuperRegion() : Reg;
182 }
183 
184 void ValistChecker::checkPreStmt(const VAArgExpr *VAA,
185  CheckerContext &C) const {
186  ProgramStateRef State = C.getState();
187  const Expr *VASubExpr = VAA->getSubExpr();
188  SVal VAListSVal = C.getSVal(VASubExpr);
189  bool Symbolic;
190  const MemRegion *VAList =
191  getVAListAsRegion(VAListSVal, VASubExpr, Symbolic, C);
192  if (!VAList)
193  return;
194  if (Symbolic)
195  return;
196  if (!State->contains<InitializedVALists>(VAList))
197  reportUninitializedAccess(
198  VAList, "va_arg() is called on an uninitialized va_list", C);
199 }
200 
201 void ValistChecker::checkDeadSymbols(SymbolReaper &SR,
202  CheckerContext &C) const {
203  ProgramStateRef State = C.getState();
204  InitializedVAListsTy TrackedVALists = State->get<InitializedVALists>();
205  RegionVector LeakedVALists;
206  for (auto Reg : TrackedVALists) {
207  if (SR.isLiveRegion(Reg))
208  continue;
209  LeakedVALists.push_back(Reg);
210  State = State->remove<InitializedVALists>(Reg);
211  }
212  if (ExplodedNode *N = C.addTransition(State))
213  reportLeakedVALists(LeakedVALists, "Initialized va_list", " is leaked", C,
214  N);
215 }
216 
217 // This function traverses the exploded graph backwards and finds the node where
218 // the va_list is initialized. That node is used for uniquing the bug paths.
219 // It is not likely that there are several different va_lists that belongs to
220 // different stack frames, so that case is not yet handled.
221 const ExplodedNode *
222 ValistChecker::getStartCallSite(const ExplodedNode *N,
223  const MemRegion *Reg) const {
224  const LocationContext *LeakContext = N->getLocationContext();
225  const ExplodedNode *StartCallNode = N;
226 
227  bool FoundInitializedState = false;
228 
229  while (N) {
230  ProgramStateRef State = N->getState();
231  if (!State->contains<InitializedVALists>(Reg)) {
232  if (FoundInitializedState)
233  break;
234  } else {
235  FoundInitializedState = true;
236  }
237  const LocationContext *NContext = N->getLocationContext();
238  if (NContext == LeakContext || NContext->isParentOf(LeakContext))
239  StartCallNode = N;
240  N = N->pred_empty() ? nullptr : *(N->pred_begin());
241  }
242 
243  return StartCallNode;
244 }
245 
246 void ValistChecker::reportUninitializedAccess(const MemRegion *VAList,
247  StringRef Msg,
248  CheckerContext &C) const {
249  if (!ChecksEnabled[CK_Uninitialized])
250  return;
251  if (ExplodedNode *N = C.generateErrorNode()) {
252  if (!BT_uninitaccess)
253  BT_uninitaccess.reset(new BugType(CheckNames[CK_Uninitialized],
254  "Uninitialized va_list",
256  auto R = llvm::make_unique<BugReport>(*BT_uninitaccess, Msg, N);
257  R->markInteresting(VAList);
258  R->addVisitor(llvm::make_unique<ValistBugVisitor>(VAList));
259  C.emitReport(std::move(R));
260  }
261 }
262 
263 void ValistChecker::reportLeakedVALists(const RegionVector &LeakedVALists,
264  StringRef Msg1, StringRef Msg2,
265  CheckerContext &C, ExplodedNode *N,
266  bool ReportUninit) const {
267  if (!(ChecksEnabled[CK_Unterminated] ||
268  (ChecksEnabled[CK_Uninitialized] && ReportUninit)))
269  return;
270  for (auto Reg : LeakedVALists) {
271  if (!BT_leakedvalist) {
272  // FIXME: maybe creating a new check name for this type of bug is a better
273  // solution.
274  BT_leakedvalist.reset(
275  new BugType(CheckNames[CK_Unterminated].getName().empty()
276  ? CheckNames[CK_Uninitialized]
277  : CheckNames[CK_Unterminated],
278  "Leaked va_list", categories::MemoryError,
279  /*SuppressOnSink=*/true));
280  }
281 
282  const ExplodedNode *StartNode = getStartCallSite(N, Reg);
283  PathDiagnosticLocation LocUsedForUniqueing;
284 
285  if (const Stmt *StartCallStmt = PathDiagnosticLocation::getStmt(StartNode))
286  LocUsedForUniqueing = PathDiagnosticLocation::createBegin(
287  StartCallStmt, C.getSourceManager(), StartNode->getLocationContext());
288 
289  SmallString<100> Buf;
290  llvm::raw_svector_ostream OS(Buf);
291  OS << Msg1;
292  std::string VariableName = Reg->getDescriptiveName();
293  if (!VariableName.empty())
294  OS << " " << VariableName;
295  OS << Msg2;
296 
297  auto R = llvm::make_unique<BugReport>(
298  *BT_leakedvalist, OS.str(), N, LocUsedForUniqueing,
299  StartNode->getLocationContext()->getDecl());
300  R->markInteresting(Reg);
301  R->addVisitor(llvm::make_unique<ValistBugVisitor>(Reg, true));
302  C.emitReport(std::move(R));
303  }
304 }
305 
306 void ValistChecker::checkVAListStartCall(const CallEvent &Call,
307  CheckerContext &C, bool IsCopy) const {
308  bool Symbolic;
309  const MemRegion *VAList =
310  getVAListAsRegion(Call.getArgSVal(0), Call.getArgExpr(0), Symbolic, C);
311  if (!VAList)
312  return;
313 
314  ProgramStateRef State = C.getState();
315 
316  if (IsCopy) {
317  const MemRegion *Arg2 =
318  getVAListAsRegion(Call.getArgSVal(1), Call.getArgExpr(1), Symbolic, C);
319  if (Arg2) {
320  if (ChecksEnabled[CK_CopyToSelf] && VAList == Arg2) {
321  RegionVector LeakedVALists{VAList};
322  if (ExplodedNode *N = C.addTransition(State))
323  reportLeakedVALists(LeakedVALists, "va_list",
324  " is copied onto itself", C, N, true);
325  return;
326  } else if (!State->contains<InitializedVALists>(Arg2) && !Symbolic) {
327  if (State->contains<InitializedVALists>(VAList)) {
328  State = State->remove<InitializedVALists>(VAList);
329  RegionVector LeakedVALists{VAList};
330  if (ExplodedNode *N = C.addTransition(State))
331  reportLeakedVALists(LeakedVALists, "Initialized va_list",
332  " is overwritten by an uninitialized one", C, N,
333  true);
334  } else {
335  reportUninitializedAccess(Arg2, "Uninitialized va_list is copied", C);
336  }
337  return;
338  }
339  }
340  }
341  if (State->contains<InitializedVALists>(VAList)) {
342  RegionVector LeakedVALists{VAList};
343  if (ExplodedNode *N = C.addTransition(State))
344  reportLeakedVALists(LeakedVALists, "Initialized va_list",
345  " is initialized again", C, N);
346  return;
347  }
348 
349  State = State->add<InitializedVALists>(VAList);
350  C.addTransition(State);
351 }
352 
353 void ValistChecker::checkVAListEndCall(const CallEvent &Call,
354  CheckerContext &C) const {
355  bool Symbolic;
356  const MemRegion *VAList =
357  getVAListAsRegion(Call.getArgSVal(0), Call.getArgExpr(0), Symbolic, C);
358  if (!VAList)
359  return;
360 
361  // We did not see va_start call, but the source of the region is unknown.
362  // Be conservative and assume the best.
363  if (Symbolic)
364  return;
365 
366  if (!C.getState()->contains<InitializedVALists>(VAList)) {
367  reportUninitializedAccess(
368  VAList, "va_end() is called on an uninitialized va_list", C);
369  return;
370  }
371  ProgramStateRef State = C.getState();
372  State = State->remove<InitializedVALists>(VAList);
373  C.addTransition(State);
374 }
375 
376 std::shared_ptr<PathDiagnosticPiece> ValistChecker::ValistBugVisitor::VisitNode(
377  const ExplodedNode *N, BugReporterContext &BRC,
378  BugReport &) {
379  ProgramStateRef State = N->getState();
380  ProgramStateRef StatePrev = N->getFirstPred()->getState();
381 
383  if (!S)
384  return nullptr;
385 
386  StringRef Msg;
387  if (State->contains<InitializedVALists>(Reg) &&
388  !StatePrev->contains<InitializedVALists>(Reg))
389  Msg = "Initialized va_list";
390  else if (!State->contains<InitializedVALists>(Reg) &&
391  StatePrev->contains<InitializedVALists>(Reg))
392  Msg = "Ended va_list";
393 
394  if (Msg.empty())
395  return nullptr;
396 
397  PathDiagnosticLocation Pos(S, BRC.getSourceManager(),
398  N->getLocationContext());
399  return std::make_shared<PathDiagnosticEventPiece>(Pos, Msg, true);
400 }
401 
402 void ento::registerValistBase(CheckerManager &mgr) {
403  mgr.registerChecker<ValistChecker>();
404 }
405 
406 bool ento::shouldRegisterValistBase(const LangOptions &LO) {
407  return true;
408 }
409 
410 #define REGISTER_CHECKER(name) \
411  void ento::register##name##Checker(CheckerManager &mgr) { \
412  ValistChecker *checker = mgr.getChecker<ValistChecker>(); \
413  checker->ChecksEnabled[ValistChecker::CK_##name] = true; \
414  checker->CheckNames[ValistChecker::CK_##name] = mgr.getCurrentCheckName(); \
415  } \
416  \
417  bool ento::shouldRegister##name##Checker(const LangOptions &LO) { \
418  return true; \
419  }
420 
422 REGISTER_CHECKER(Unterminated)
423 REGISTER_CHECKER(CopyToSelf)
A (possibly-)qualified type.
Definition: Type.h:643
Stmt - This represents one statement.
Definition: Stmt.h:66
QualType getPointeeType() const
If this is a pointer, ObjC object pointer, or block pointer, this returns the respective pointee...
Definition: Type.cpp:505
bool isRecordType() const
Definition: Type.h:6464
IntrusiveRefCntPtr< const ProgramState > ProgramStateRef
const Expr * getSubExpr() const
Definition: Expr.h:4230
bool isParentOf(const LocationContext *LC) const
LineState State
Keeps track of the various options that can be enabled, which controls the dialect of C or C++ that i...
Definition: LangOptions.h:49
This class represents a description of a function call using the number of arguments and the name of ...
Definition: CallEvent.h:1057
Represents a call to the builtin function __builtin_va_arg.
Definition: Expr.h:4212
This represents one expression.
Definition: Expr.h:108
static const Stmt * getStmt(const ExplodedNode *N)
Given an exploded node, retrieve the statement that should be used for the diagnostic location...
static PathDiagnosticLocation createBegin(const Decl *D, const SourceManager &SM)
Create a location for the beginning of the declaration.
#define REGISTER_SET_WITH_PROGRAMSTATE(Name, Elem)
Declares an immutable set of type NameTy, suitable for placement into the ProgramState.
Dataflow Directional Tag Classes.
static std::string getName(const CallEvent &Call)
Indicates that the tracking object is a descendant of a referenced-counted OSObject, used in the Darwin kernel.
#define REGISTER_CHECKER(name)
X
Add a minimal nested name specifier fixit hint to allow lookup of a tag name from an outer enclosing ...
Definition: SemaDecl.cpp:14445
static PathDiagnosticLocation createEndOfPath(const ExplodedNode *N, const SourceManager &SM)
Create a location corresponding to the next valid ExplodedNode as end of path location.
bool isPointerType() const
Definition: Type.h:6384