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
= {{{CDM::CLibrary
, {"vfprintf"}, 3}, 2},
104 {{CDM::CLibrary
, {"vfscanf"}, 3}, 2},
105 {{CDM::CLibrary
, {"vprintf"}, 2}, 1},
106 {{CDM::CLibrary
, {"vscanf"}, 2}, 1},
107 {{CDM::CLibrary
, {"vsnprintf"}, 4}, 3},
108 {{CDM::CLibrary
, {"vsprintf"}, 3}, 2},
109 {{CDM::CLibrary
, {"vsscanf"}, 3}, 2},
110 {{CDM::CLibrary
, {"vfwprintf"}, 3}, 2},
111 {{CDM::CLibrary
, {"vfwscanf"}, 3}, 2},
112 {{CDM::CLibrary
, {"vwprintf"}, 2}, 1},
113 {{CDM::CLibrary
, {"vwscanf"}, 2}, 1},
114 {{CDM::CLibrary
, {"vswprintf"}, 4}, 3},
115 // vswprintf is the wide version of
116 // vsnprintf, vsprintf has no wide version
117 {{CDM::CLibrary
, {"vswscanf"}, 3}, 2}};
119 const CallDescription
ValistChecker::VaStart(CDM::CLibrary
,
120 {"__builtin_va_start"}, /*Args=*/2,
122 ValistChecker::VaCopy(CDM::CLibrary
, {"__builtin_va_copy"}, 2),
123 ValistChecker::VaEnd(CDM::CLibrary
, {"__builtin_va_end"}, 1);
124 } // end anonymous namespace
126 void ValistChecker::checkPreCall(const CallEvent
&Call
,
127 CheckerContext
&C
) const {
128 if (VaStart
.matches(Call
))
129 checkVAListStartCall(Call
, C
, false);
130 else if (VaCopy
.matches(Call
))
131 checkVAListStartCall(Call
, C
, true);
132 else if (VaEnd
.matches(Call
))
133 checkVAListEndCall(Call
, C
);
135 for (auto FuncInfo
: VAListAccepters
) {
136 if (!FuncInfo
.Func
.matches(Call
))
139 const MemRegion
*VAList
=
140 getVAListAsRegion(Call
.getArgSVal(FuncInfo
.VAListPos
),
141 Call
.getArgExpr(FuncInfo
.VAListPos
), Symbolic
, C
);
145 if (C
.getState()->contains
<InitializedVALists
>(VAList
))
148 // We did not see va_start call, but the source of the region is unknown.
149 // Be conservative and assume the best.
153 SmallString
<80> Errmsg("Function '");
154 Errmsg
+= FuncInfo
.Func
.getFunctionName();
155 Errmsg
+= "' is called with an uninitialized va_list argument";
156 reportUninitializedAccess(VAList
, Errmsg
.c_str(), C
);
162 const MemRegion
*ValistChecker::getVAListAsRegion(SVal SV
, const Expr
*E
,
164 CheckerContext
&C
) const {
165 const MemRegion
*Reg
= SV
.getAsRegion();
168 // TODO: In the future this should be abstracted away by the analyzer.
169 bool VaListModelledAsArray
= false;
170 if (const auto *Cast
= dyn_cast
<CastExpr
>(E
)) {
171 QualType Ty
= Cast
->getType();
172 VaListModelledAsArray
=
173 Ty
->isPointerType() && Ty
->getPointeeType()->isRecordType();
175 if (const auto *DeclReg
= Reg
->getAs
<DeclRegion
>()) {
176 if (isa
<ParmVarDecl
>(DeclReg
->getDecl()))
177 Reg
= C
.getState()->getSVal(SV
.castAs
<Loc
>()).getAsRegion();
179 IsSymbolic
= Reg
&& Reg
->getBaseRegion()->getAs
<SymbolicRegion
>();
180 // Some VarRegion based VA lists reach here as ElementRegions.
181 const auto *EReg
= dyn_cast_or_null
<ElementRegion
>(Reg
);
182 return (EReg
&& VaListModelledAsArray
) ? EReg
->getSuperRegion() : Reg
;
185 void ValistChecker::checkPreStmt(const VAArgExpr
*VAA
,
186 CheckerContext
&C
) const {
187 ProgramStateRef State
= C
.getState();
188 const Expr
*VASubExpr
= VAA
->getSubExpr();
189 SVal VAListSVal
= C
.getSVal(VASubExpr
);
191 const MemRegion
*VAList
=
192 getVAListAsRegion(VAListSVal
, VASubExpr
, Symbolic
, C
);
197 if (!State
->contains
<InitializedVALists
>(VAList
))
198 reportUninitializedAccess(
199 VAList
, "va_arg() is called on an uninitialized va_list", C
);
202 void ValistChecker::checkDeadSymbols(SymbolReaper
&SR
,
203 CheckerContext
&C
) const {
204 ProgramStateRef State
= C
.getState();
205 InitializedVAListsTy TrackedVALists
= State
->get
<InitializedVALists
>();
206 RegionVector LeakedVALists
;
207 for (auto Reg
: TrackedVALists
) {
208 if (SR
.isLiveRegion(Reg
))
210 LeakedVALists
.push_back(Reg
);
211 State
= State
->remove
<InitializedVALists
>(Reg
);
213 if (ExplodedNode
*N
= C
.addTransition(State
))
214 reportLeakedVALists(LeakedVALists
, "Initialized va_list", " is leaked", C
,
218 // This function traverses the exploded graph backwards and finds the node where
219 // the va_list is initialized. That node is used for uniquing the bug paths.
220 // It is not likely that there are several different va_lists that belongs to
221 // different stack frames, so that case is not yet handled.
223 ValistChecker::getStartCallSite(const ExplodedNode
*N
,
224 const MemRegion
*Reg
) const {
225 const LocationContext
*LeakContext
= N
->getLocationContext();
226 const ExplodedNode
*StartCallNode
= N
;
228 bool FoundInitializedState
= false;
231 ProgramStateRef State
= N
->getState();
232 if (!State
->contains
<InitializedVALists
>(Reg
)) {
233 if (FoundInitializedState
)
236 FoundInitializedState
= true;
238 const LocationContext
*NContext
= N
->getLocationContext();
239 if (NContext
== LeakContext
|| NContext
->isParentOf(LeakContext
))
241 N
= N
->pred_empty() ? nullptr : *(N
->pred_begin());
244 return StartCallNode
;
247 void ValistChecker::reportUninitializedAccess(const MemRegion
*VAList
,
249 CheckerContext
&C
) const {
250 if (!ChecksEnabled
[CK_Uninitialized
])
252 if (ExplodedNode
*N
= C
.generateErrorNode()) {
253 if (!BT_uninitaccess
)
254 BT_uninitaccess
.reset(new BugType(CheckNames
[CK_Uninitialized
],
255 "Uninitialized va_list",
256 categories::MemoryError
));
257 auto R
= std::make_unique
<PathSensitiveBugReport
>(*BT_uninitaccess
, Msg
, N
);
258 R
->markInteresting(VAList
);
259 R
->addVisitor(std::make_unique
<ValistBugVisitor
>(VAList
));
260 C
.emitReport(std::move(R
));
264 void ValistChecker::reportLeakedVALists(const RegionVector
&LeakedVALists
,
265 StringRef Msg1
, StringRef Msg2
,
266 CheckerContext
&C
, ExplodedNode
*N
,
267 bool ReportUninit
) const {
268 if (!(ChecksEnabled
[CK_Unterminated
] ||
269 (ChecksEnabled
[CK_Uninitialized
] && ReportUninit
)))
271 for (auto Reg
: LeakedVALists
) {
272 if (!BT_leakedvalist
) {
273 // FIXME: maybe creating a new check name for this type of bug is a better
275 BT_leakedvalist
.reset(
276 new BugType(CheckNames
[CK_Unterminated
].getName().empty()
277 ? CheckNames
[CK_Uninitialized
]
278 : CheckNames
[CK_Unterminated
],
279 "Leaked va_list", categories::MemoryError
,
280 /*SuppressOnSink=*/true));
283 const ExplodedNode
*StartNode
= getStartCallSite(N
, Reg
);
284 PathDiagnosticLocation LocUsedForUniqueing
;
286 if (const Stmt
*StartCallStmt
= StartNode
->getStmtForDiagnostics())
287 LocUsedForUniqueing
= PathDiagnosticLocation::createBegin(
288 StartCallStmt
, C
.getSourceManager(), StartNode
->getLocationContext());
290 SmallString
<100> Buf
;
291 llvm::raw_svector_ostream
OS(Buf
);
293 std::string VariableName
= Reg
->getDescriptiveName();
294 if (!VariableName
.empty())
295 OS
<< " " << VariableName
;
298 auto R
= std::make_unique
<PathSensitiveBugReport
>(
299 *BT_leakedvalist
, OS
.str(), N
, LocUsedForUniqueing
,
300 StartNode
->getLocationContext()->getDecl());
301 R
->markInteresting(Reg
);
302 R
->addVisitor(std::make_unique
<ValistBugVisitor
>(Reg
, true));
303 C
.emitReport(std::move(R
));
307 void ValistChecker::checkVAListStartCall(const CallEvent
&Call
,
308 CheckerContext
&C
, bool IsCopy
) const {
310 const MemRegion
*VAList
=
311 getVAListAsRegion(Call
.getArgSVal(0), Call
.getArgExpr(0), Symbolic
, C
);
315 ProgramStateRef State
= C
.getState();
318 const MemRegion
*Arg2
=
319 getVAListAsRegion(Call
.getArgSVal(1), Call
.getArgExpr(1), Symbolic
, C
);
321 if (ChecksEnabled
[CK_CopyToSelf
] && VAList
== Arg2
) {
322 RegionVector LeakedVALists
{VAList
};
323 if (ExplodedNode
*N
= C
.addTransition(State
))
324 reportLeakedVALists(LeakedVALists
, "va_list",
325 " is copied onto itself", C
, N
, true);
327 } else if (!State
->contains
<InitializedVALists
>(Arg2
) && !Symbolic
) {
328 if (State
->contains
<InitializedVALists
>(VAList
)) {
329 State
= State
->remove
<InitializedVALists
>(VAList
);
330 RegionVector LeakedVALists
{VAList
};
331 if (ExplodedNode
*N
= C
.addTransition(State
))
332 reportLeakedVALists(LeakedVALists
, "Initialized va_list",
333 " is overwritten by an uninitialized one", C
, N
,
336 reportUninitializedAccess(Arg2
, "Uninitialized va_list is copied", C
);
342 if (State
->contains
<InitializedVALists
>(VAList
)) {
343 RegionVector LeakedVALists
{VAList
};
344 if (ExplodedNode
*N
= C
.addTransition(State
))
345 reportLeakedVALists(LeakedVALists
, "Initialized va_list",
346 " is initialized again", C
, N
);
350 State
= State
->add
<InitializedVALists
>(VAList
);
351 C
.addTransition(State
);
354 void ValistChecker::checkVAListEndCall(const CallEvent
&Call
,
355 CheckerContext
&C
) const {
357 const MemRegion
*VAList
=
358 getVAListAsRegion(Call
.getArgSVal(0), Call
.getArgExpr(0), Symbolic
, C
);
362 // We did not see va_start call, but the source of the region is unknown.
363 // Be conservative and assume the best.
367 if (!C
.getState()->contains
<InitializedVALists
>(VAList
)) {
368 reportUninitializedAccess(
369 VAList
, "va_end() is called on an uninitialized va_list", C
);
372 ProgramStateRef State
= C
.getState();
373 State
= State
->remove
<InitializedVALists
>(VAList
);
374 C
.addTransition(State
);
377 PathDiagnosticPieceRef
ValistChecker::ValistBugVisitor::VisitNode(
378 const ExplodedNode
*N
, BugReporterContext
&BRC
, PathSensitiveBugReport
&) {
379 ProgramStateRef State
= N
->getState();
380 ProgramStateRef StatePrev
= N
->getFirstPred()->getState();
382 const Stmt
*S
= N
->getStmtForDiagnostics();
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";
397 PathDiagnosticLocation
Pos(S
, BRC
.getSourceManager(),
398 N
->getLocationContext());
399 return std::make_shared
<PathDiagnosticEventPiece
>(Pos
, Msg
, true);
402 void ento::registerValistBase(CheckerManager
&mgr
) {
403 mgr
.registerChecker
<ValistChecker
>();
406 bool ento::shouldRegisterValistBase(const CheckerManager
&mgr
) {
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] = \
415 mgr.getCurrentCheckerName(); \
418 bool ento::shouldRegister##name##Checker(const CheckerManager &mgr) { \
422 REGISTER_CHECKER(Uninitialized
)
423 REGISTER_CHECKER(Unterminated
)
424 REGISTER_CHECKER(CopyToSelf
)