1 //== ValistChecker.cpp - stdarg.h macro usage checker -----------*- C++ -*--==//
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
7 //===----------------------------------------------------------------------===//
9 // This defines checkers which detect usage of uninitialized va_list values
10 // and va_start calls with no matching va_end.
12 //===----------------------------------------------------------------------===//
14 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
15 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
16 #include "clang/StaticAnalyzer/Core/Checker.h"
17 #include "clang/StaticAnalyzer/Core/CheckerManager.h"
18 #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
19 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
20 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
22 using namespace clang
;
25 REGISTER_SET_WITH_PROGRAMSTATE(InitializedVALists
, const MemRegion
*)
28 typedef SmallVector
<const MemRegion
*, 2> RegionVector
;
30 class ValistChecker
: public Checker
<check::PreCall
, check::PreStmt
<VAArgExpr
>,
32 mutable std::unique_ptr
<BugType
> BT_leakedvalist
, BT_uninitaccess
;
34 struct VAListAccepter
{
38 static const SmallVector
<VAListAccepter
, 15> VAListAccepters
;
39 static const CallDescription VaStart
, VaEnd
, VaCopy
;
49 bool ChecksEnabled
[CK_NumCheckKinds
] = {false};
50 CheckerNameRef CheckNames
[CK_NumCheckKinds
];
52 void checkPreStmt(const VAArgExpr
*VAA
, CheckerContext
&C
) const;
53 void checkPreCall(const CallEvent
&Call
, CheckerContext
&C
) const;
54 void checkDeadSymbols(SymbolReaper
&SR
, CheckerContext
&C
) const;
57 const MemRegion
*getVAListAsRegion(SVal SV
, const Expr
*VAExpr
,
58 bool &IsSymbolic
, CheckerContext
&C
) const;
59 const ExplodedNode
*getStartCallSite(const ExplodedNode
*N
,
60 const MemRegion
*Reg
) const;
62 void reportUninitializedAccess(const MemRegion
*VAList
, StringRef Msg
,
63 CheckerContext
&C
) const;
64 void reportLeakedVALists(const RegionVector
&LeakedVALists
, StringRef Msg1
,
65 StringRef Msg2
, CheckerContext
&C
, ExplodedNode
*N
,
66 bool ReportUninit
= false) const;
68 void checkVAListStartCall(const CallEvent
&Call
, CheckerContext
&C
,
70 void checkVAListEndCall(const CallEvent
&Call
, CheckerContext
&C
) const;
72 class ValistBugVisitor
: public BugReporterVisitor
{
74 ValistBugVisitor(const MemRegion
*Reg
, bool IsLeak
= false)
75 : Reg(Reg
), IsLeak(IsLeak
) {}
76 void Profile(llvm::FoldingSetNodeID
&ID
) const override
{
81 PathDiagnosticPieceRef
getEndPath(BugReporterContext
&BRC
,
82 const ExplodedNode
*EndPathNode
,
83 PathSensitiveBugReport
&BR
) override
{
87 PathDiagnosticLocation L
= BR
.getLocation();
88 // Do not add the statement itself as a range in case of leak.
89 return std::make_shared
<PathDiagnosticEventPiece
>(L
, BR
.getDescription(),
92 PathDiagnosticPieceRef
VisitNode(const ExplodedNode
*N
,
93 BugReporterContext
&BRC
,
94 PathSensitiveBugReport
&BR
) override
;
102 const SmallVector
<ValistChecker::VAListAccepter
, 15>
103 ValistChecker::VAListAccepters
= {{{{"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
116 // vsnprintf, vsprintf has no wide version
117 {{{"vswscanf"}, 3}, 2}};
119 const CallDescription
ValistChecker::VaStart({"__builtin_va_start"}, /*Args=*/2,
121 ValistChecker::VaCopy({"__builtin_va_copy"}, 2),
122 ValistChecker::VaEnd({"__builtin_va_end"}, 1);
123 } // end anonymous namespace
125 void ValistChecker::checkPreCall(const CallEvent
&Call
,
126 CheckerContext
&C
) const {
127 if (!Call
.isGlobalCFunction())
129 if (VaStart
.matches(Call
))
130 checkVAListStartCall(Call
, C
, false);
131 else if (VaCopy
.matches(Call
))
132 checkVAListStartCall(Call
, C
, true);
133 else if (VaEnd
.matches(Call
))
134 checkVAListEndCall(Call
, C
);
136 for (auto FuncInfo
: VAListAccepters
) {
137 if (!FuncInfo
.Func
.matches(Call
))
140 const MemRegion
*VAList
=
141 getVAListAsRegion(Call
.getArgSVal(FuncInfo
.VAListPos
),
142 Call
.getArgExpr(FuncInfo
.VAListPos
), Symbolic
, C
);
146 if (C
.getState()->contains
<InitializedVALists
>(VAList
))
149 // We did not see va_start call, but the source of the region is unknown.
150 // Be conservative and assume the best.
154 SmallString
<80> Errmsg("Function '");
155 Errmsg
+= FuncInfo
.Func
.getFunctionName();
156 Errmsg
+= "' is called with an uninitialized va_list argument";
157 reportUninitializedAccess(VAList
, Errmsg
.c_str(), C
);
163 const MemRegion
*ValistChecker::getVAListAsRegion(SVal SV
, const Expr
*E
,
165 CheckerContext
&C
) const {
166 const MemRegion
*Reg
= SV
.getAsRegion();
169 // TODO: In the future this should be abstracted away by the analyzer.
170 bool VaListModelledAsArray
= false;
171 if (const auto *Cast
= dyn_cast
<CastExpr
>(E
)) {
172 QualType Ty
= Cast
->getType();
173 VaListModelledAsArray
=
174 Ty
->isPointerType() && Ty
->getPointeeType()->isRecordType();
176 if (const auto *DeclReg
= Reg
->getAs
<DeclRegion
>()) {
177 if (isa
<ParmVarDecl
>(DeclReg
->getDecl()))
178 Reg
= C
.getState()->getSVal(SV
.castAs
<Loc
>()).getAsRegion();
180 IsSymbolic
= Reg
&& Reg
->getBaseRegion()->getAs
<SymbolicRegion
>();
181 // Some VarRegion based VA lists reach here as ElementRegions.
182 const auto *EReg
= dyn_cast_or_null
<ElementRegion
>(Reg
);
183 return (EReg
&& VaListModelledAsArray
) ? EReg
->getSuperRegion() : Reg
;
186 void ValistChecker::checkPreStmt(const VAArgExpr
*VAA
,
187 CheckerContext
&C
) const {
188 ProgramStateRef State
= C
.getState();
189 const Expr
*VASubExpr
= VAA
->getSubExpr();
190 SVal VAListSVal
= C
.getSVal(VASubExpr
);
192 const MemRegion
*VAList
=
193 getVAListAsRegion(VAListSVal
, VASubExpr
, Symbolic
, C
);
198 if (!State
->contains
<InitializedVALists
>(VAList
))
199 reportUninitializedAccess(
200 VAList
, "va_arg() is called on an uninitialized va_list", C
);
203 void ValistChecker::checkDeadSymbols(SymbolReaper
&SR
,
204 CheckerContext
&C
) const {
205 ProgramStateRef State
= C
.getState();
206 InitializedVAListsTy TrackedVALists
= State
->get
<InitializedVALists
>();
207 RegionVector LeakedVALists
;
208 for (auto Reg
: TrackedVALists
) {
209 if (SR
.isLiveRegion(Reg
))
211 LeakedVALists
.push_back(Reg
);
212 State
= State
->remove
<InitializedVALists
>(Reg
);
214 if (ExplodedNode
*N
= C
.addTransition(State
))
215 reportLeakedVALists(LeakedVALists
, "Initialized va_list", " is leaked", C
,
219 // This function traverses the exploded graph backwards and finds the node where
220 // the va_list is initialized. That node is used for uniquing the bug paths.
221 // It is not likely that there are several different va_lists that belongs to
222 // different stack frames, so that case is not yet handled.
224 ValistChecker::getStartCallSite(const ExplodedNode
*N
,
225 const MemRegion
*Reg
) const {
226 const LocationContext
*LeakContext
= N
->getLocationContext();
227 const ExplodedNode
*StartCallNode
= N
;
229 bool FoundInitializedState
= false;
232 ProgramStateRef State
= N
->getState();
233 if (!State
->contains
<InitializedVALists
>(Reg
)) {
234 if (FoundInitializedState
)
237 FoundInitializedState
= true;
239 const LocationContext
*NContext
= N
->getLocationContext();
240 if (NContext
== LeakContext
|| NContext
->isParentOf(LeakContext
))
242 N
= N
->pred_empty() ? nullptr : *(N
->pred_begin());
245 return StartCallNode
;
248 void ValistChecker::reportUninitializedAccess(const MemRegion
*VAList
,
250 CheckerContext
&C
) const {
251 if (!ChecksEnabled
[CK_Uninitialized
])
253 if (ExplodedNode
*N
= C
.generateErrorNode()) {
254 if (!BT_uninitaccess
)
255 BT_uninitaccess
.reset(new BugType(CheckNames
[CK_Uninitialized
],
256 "Uninitialized va_list",
257 categories::MemoryError
));
258 auto R
= std::make_unique
<PathSensitiveBugReport
>(*BT_uninitaccess
, Msg
, N
);
259 R
->markInteresting(VAList
);
260 R
->addVisitor(std::make_unique
<ValistBugVisitor
>(VAList
));
261 C
.emitReport(std::move(R
));
265 void ValistChecker::reportLeakedVALists(const RegionVector
&LeakedVALists
,
266 StringRef Msg1
, StringRef Msg2
,
267 CheckerContext
&C
, ExplodedNode
*N
,
268 bool ReportUninit
) const {
269 if (!(ChecksEnabled
[CK_Unterminated
] ||
270 (ChecksEnabled
[CK_Uninitialized
] && ReportUninit
)))
272 for (auto Reg
: LeakedVALists
) {
273 if (!BT_leakedvalist
) {
274 // FIXME: maybe creating a new check name for this type of bug is a better
276 BT_leakedvalist
.reset(
277 new BugType(CheckNames
[CK_Unterminated
].getName().empty()
278 ? CheckNames
[CK_Uninitialized
]
279 : CheckNames
[CK_Unterminated
],
280 "Leaked va_list", categories::MemoryError
,
281 /*SuppressOnSink=*/true));
284 const ExplodedNode
*StartNode
= getStartCallSite(N
, Reg
);
285 PathDiagnosticLocation LocUsedForUniqueing
;
287 if (const Stmt
*StartCallStmt
= StartNode
->getStmtForDiagnostics())
288 LocUsedForUniqueing
= PathDiagnosticLocation::createBegin(
289 StartCallStmt
, C
.getSourceManager(), StartNode
->getLocationContext());
291 SmallString
<100> Buf
;
292 llvm::raw_svector_ostream
OS(Buf
);
294 std::string VariableName
= Reg
->getDescriptiveName();
295 if (!VariableName
.empty())
296 OS
<< " " << VariableName
;
299 auto R
= std::make_unique
<PathSensitiveBugReport
>(
300 *BT_leakedvalist
, OS
.str(), N
, LocUsedForUniqueing
,
301 StartNode
->getLocationContext()->getDecl());
302 R
->markInteresting(Reg
);
303 R
->addVisitor(std::make_unique
<ValistBugVisitor
>(Reg
, true));
304 C
.emitReport(std::move(R
));
308 void ValistChecker::checkVAListStartCall(const CallEvent
&Call
,
309 CheckerContext
&C
, bool IsCopy
) const {
311 const MemRegion
*VAList
=
312 getVAListAsRegion(Call
.getArgSVal(0), Call
.getArgExpr(0), Symbolic
, C
);
316 ProgramStateRef State
= C
.getState();
319 const MemRegion
*Arg2
=
320 getVAListAsRegion(Call
.getArgSVal(1), Call
.getArgExpr(1), Symbolic
, C
);
322 if (ChecksEnabled
[CK_CopyToSelf
] && VAList
== Arg2
) {
323 RegionVector LeakedVALists
{VAList
};
324 if (ExplodedNode
*N
= C
.addTransition(State
))
325 reportLeakedVALists(LeakedVALists
, "va_list",
326 " is copied onto itself", C
, N
, true);
328 } else if (!State
->contains
<InitializedVALists
>(Arg2
) && !Symbolic
) {
329 if (State
->contains
<InitializedVALists
>(VAList
)) {
330 State
= State
->remove
<InitializedVALists
>(VAList
);
331 RegionVector LeakedVALists
{VAList
};
332 if (ExplodedNode
*N
= C
.addTransition(State
))
333 reportLeakedVALists(LeakedVALists
, "Initialized va_list",
334 " is overwritten by an uninitialized one", C
, N
,
337 reportUninitializedAccess(Arg2
, "Uninitialized va_list is copied", C
);
343 if (State
->contains
<InitializedVALists
>(VAList
)) {
344 RegionVector LeakedVALists
{VAList
};
345 if (ExplodedNode
*N
= C
.addTransition(State
))
346 reportLeakedVALists(LeakedVALists
, "Initialized va_list",
347 " is initialized again", C
, N
);
351 State
= State
->add
<InitializedVALists
>(VAList
);
352 C
.addTransition(State
);
355 void ValistChecker::checkVAListEndCall(const CallEvent
&Call
,
356 CheckerContext
&C
) const {
358 const MemRegion
*VAList
=
359 getVAListAsRegion(Call
.getArgSVal(0), Call
.getArgExpr(0), Symbolic
, C
);
363 // We did not see va_start call, but the source of the region is unknown.
364 // Be conservative and assume the best.
368 if (!C
.getState()->contains
<InitializedVALists
>(VAList
)) {
369 reportUninitializedAccess(
370 VAList
, "va_end() is called on an uninitialized va_list", C
);
373 ProgramStateRef State
= C
.getState();
374 State
= State
->remove
<InitializedVALists
>(VAList
);
375 C
.addTransition(State
);
378 PathDiagnosticPieceRef
ValistChecker::ValistBugVisitor::VisitNode(
379 const ExplodedNode
*N
, BugReporterContext
&BRC
, PathSensitiveBugReport
&) {
380 ProgramStateRef State
= N
->getState();
381 ProgramStateRef StatePrev
= N
->getFirstPred()->getState();
383 const Stmt
*S
= N
->getStmtForDiagnostics();
388 if (State
->contains
<InitializedVALists
>(Reg
) &&
389 !StatePrev
->contains
<InitializedVALists
>(Reg
))
390 Msg
= "Initialized va_list";
391 else if (!State
->contains
<InitializedVALists
>(Reg
) &&
392 StatePrev
->contains
<InitializedVALists
>(Reg
))
393 Msg
= "Ended va_list";
398 PathDiagnosticLocation
Pos(S
, BRC
.getSourceManager(),
399 N
->getLocationContext());
400 return std::make_shared
<PathDiagnosticEventPiece
>(Pos
, Msg
, true);
403 void ento::registerValistBase(CheckerManager
&mgr
) {
404 mgr
.registerChecker
<ValistChecker
>();
407 bool ento::shouldRegisterValistBase(const CheckerManager
&mgr
) {
411 #define REGISTER_CHECKER(name) \
412 void ento::register##name##Checker(CheckerManager &mgr) { \
413 ValistChecker *checker = mgr.getChecker<ValistChecker>(); \
414 checker->ChecksEnabled[ValistChecker::CK_##name] = true; \
415 checker->CheckNames[ValistChecker::CK_##name] = \
416 mgr.getCurrentCheckerName(); \
419 bool ento::shouldRegister##name##Checker(const CheckerManager &mgr) { \
423 REGISTER_CHECKER(Uninitialized
)
424 REGISTER_CHECKER(Unterminated
)
425 REGISTER_CHECKER(CopyToSelf
)