clang 23.0.0git
UnconditionalVAArgChecker.cpp
Go to the documentation of this file.
1//== UnconditionalVAArgChecker.cpp -----------------------------------------==//
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 a checker to detect functions that unconditionally call
10// va_arg() and would fail if they were called with zero variadic arguments.
11//
12// This checker is only partially path-sensitive: it relies on the symbolic
13// execution of the analyzer engine to follow the execution path from the
14// beginning of a function to a va_arg() call and determine whether there are
15// any branching points on that path -- but it uses BasicBugReport reports
16// because report path wouldn't contain any useful information. (As this
17// checker diagnoses a property of a variadic function, the path before that
18// function is irrelevant; then the unconditional part of path is trivial.)
19//
20// The AST matching framework of Clang Tidy is not powerful enough to express
21// this "no branching on the execution path" relationship, at least not without
22// reimplementing a crude and fragile form of symbolic execution.
23//
24//===----------------------------------------------------------------------===//
25
33#include "llvm/Support/FormatVariadic.h"
34
35using namespace clang;
36using namespace ento;
37using llvm::formatv;
38
39/// Either nullptr or a function under symbolic execution; a non-null value
40/// means that the analyzer didn't see any branching points from the beginning
41/// of that function until the current location.
42REGISTER_TRAIT_WITH_PROGRAMSTATE(HasUnconditionalPath, const FunctionDecl *)
43
44namespace {
45class UnconditionalVAArgChecker
46 : public Checker<check::BeginFunction, check::EndFunction,
47 check::BranchCondition, check::PreStmt<VAArgExpr>> {
48 const BugType BT{this, "Unconditional use of va_arg()",
50
51 static const FunctionDecl *getCurrentFunction(CheckerContext &C);
52
53public:
54 void checkBeginFunction(CheckerContext &C) const;
55 void checkEndFunction(const ReturnStmt *, CheckerContext &C) const;
56 void checkBranchCondition(const Stmt *Condition, CheckerContext &C) const;
57 void checkPreStmt(const VAArgExpr *VAA, CheckerContext &C) const;
58};
59} // end anonymous namespace
60
61const FunctionDecl *
62UnconditionalVAArgChecker::getCurrentFunction(CheckerContext &C) {
63 const Decl *FD = C.getLocationContext()->getDecl();
64 return dyn_cast_if_present<FunctionDecl>(FD);
65}
66
67void UnconditionalVAArgChecker::checkBeginFunction(CheckerContext &C) const {
68 // We only look for unconditional va_arg() use in variadic functions.
69 // Functions that take a va_list argument are just parts of the argument
70 // handling, it is more natural for them to have preconditions.
71 const FunctionDecl *FD = getCurrentFunction(C);
72 if (FD && FD->isVariadic()) {
73 // If a variadic function is inlined in the body of another variadic
74 // function, this overwrites the path tracking for the outer function. As
75 // this situation is fairly rare and it is very unlikely that the "big"
76 // outer function still has an unconditional path, there is no need to
77 // write more complex logic that handles this.
78 // NOTE: Despite this, the checker can sometimes still report the
79 // unconditional va_arg() use in the outer function (probably because there
80 // is an alternative execution path that doesn't enter the inner call).
81 C.addTransition(C.getState()->set<HasUnconditionalPath>(FD));
82 }
83}
84
85void UnconditionalVAArgChecker::checkEndFunction(const ReturnStmt *,
86 CheckerContext &C) const {
87 // This callback is just for the sake of cleanliness, to remove data from the
88 // State after it becomes irrelevant. This checker would function perfectly
89 // correctly without this callback, and the impact on other checkers is also
90 // extremely limited (presence of extra metadata might prevent the
91 // unification of execution paths in some very rare situations).
92 ProgramStateRef State = C.getState();
93 const FunctionDecl *FD = getCurrentFunction(C);
94 if (FD && FD == State->get<HasUnconditionalPath>()) {
95 State = State->set<HasUnconditionalPath>(nullptr);
96 C.addTransition(State);
97 }
98}
99
100void UnconditionalVAArgChecker::checkBranchCondition(const Stmt *Condition,
101 CheckerContext &C) const {
102 // After evaluating a branch condition, the analyzer (which examines
103 // execution paths individually) won't be able to find a va_arg() expression
104 // that is _unconditionally_ reached -- so this callback resets the state
105 // trait HasUnconditionalPath.
106 // NOTES:
107 // 1. This is the right thing to do even if the analyzer sees that _in the
108 // current state_ the execution can only continue in one direction. For
109 // example, if the variadic function isn't the entrypont, then the parameters
110 // recieved from the caller may guarantee that va_arg() is used -- but this
111 // does not mean that the function _unconditionally_ uses va_arg().
112 // 2. After other kinds of state splits (e.g. EagerlyAssueme, callbacks of
113 // StdLibraryFunctions separating different cases for the behavior of a
114 // library function etc.) the different execution paths will follow the same
115 // code (until they hit a branch condition), so it is reasonable (although
116 // not always correct) to assume that a va_arg() reached after those state
117 // splits is still _unconditionally_ reached if there were no branching
118 // statements.
119 // 3. This checker activates _after_ the evaluation of the branch condition,
120 // so va_arg() in the branch condition can be unconditionally reached.
121 C.addTransition(C.getState()->set<HasUnconditionalPath>(nullptr));
122}
123
124void UnconditionalVAArgChecker::checkPreStmt(const VAArgExpr *VAA,
125 CheckerContext &C) const {
126 ProgramStateRef State = C.getState();
127 const FunctionDecl *PathFrom = State->get<HasUnconditionalPath>();
128 if (!PathFrom)
129 return;
130
131 // Reset this trait in the state to ensure that multiple consecutive
132 // va_arg() calls don't produce repeated warnings.
133 C.addTransition(State->set<HasUnconditionalPath>(nullptr));
134
135 IdentifierInfo *II = PathFrom->getIdentifier();
136 if (!II)
137 return;
138 StringRef FN = II->getName();
139
140 std::string FullMsg = formatv(
141 "Calls to '{0}' always reach this va_arg() expression, so calling "
142 "'{0}' with no variadic arguments would be undefined behavior",
143 FN);
144 SourceRange SR = VAA->getSourceRange();
145 PathDiagnosticLocation PDL(SR.getBegin(),
146 C.getASTContext().getSourceManager());
147 // We create a non-path-sensitive report because the path wouldn't contain
148 // any useful information: the path leading to the variadic function is
149 // actively ignored by the checker and the unconditional path from the
150 // start of the variadic function is trivial.
151 auto R =
152 std::make_unique<BasicBugReport>(BT, BT.getDescription(), FullMsg, PDL);
153
154 if (getCurrentFunction(C) != PathFrom) {
155 // Highlight the definition of the variadic function in the rare case
156 // when the reached va_arg() expression is in another function.
157 SourceRange DefSR = PathFrom->getSourceRange();
158 PathDiagnosticLocation DefPDL(DefSR.getBegin(),
159 C.getASTContext().getSourceManager());
160 std::string NoteMsg =
161 formatv("Variadic function '{0}' is defined here", FN);
162 R->addNote(NoteMsg, DefPDL, DefSR);
163 }
164 R->addRange(SR);
165 R->setDeclWithIssue(PathFrom);
166 C.emitReport(std::move(R));
167}
168
169void ento::registerUnconditionalVAArgChecker(CheckerManager &Mgr) {
170 Mgr.registerChecker<UnconditionalVAArgChecker>();
171}
172
173bool ento::shouldRegisterUnconditionalVAArgChecker(const CheckerManager &) {
174 return true;
175}
#define REGISTER_TRAIT_WITH_PROGRAMSTATE(Name, Type)
Declares a program state trait for type Type called Name, and introduce a type named NameTy.
Represents a function declaration or definition.
Definition Decl.h:2000
bool isVariadic() const
Whether this function is variadic.
Definition Decl.cpp:3134
SourceRange getSourceRange() const override LLVM_READONLY
Source range that this declaration covers.
Definition Decl.cpp:4550
StringRef getName() const
Return the actual identifier string.
IdentifierInfo * getIdentifier() const
Get the identifier that names this declaration, if there is one.
Definition Decl.h:295
SourceLocation getBegin() const
SourceRange getSourceRange() const LLVM_READONLY
SourceLocation tokens are not useful in isolation - they are low level value objects created/interpre...
Definition Stmt.cpp:343
StringRef getDescription() const
Definition BugType.h:58
CHECKER * registerChecker(AT &&...Args)
Register a single-part checker (derived from Checker): construct its singleton instance,...
Simple checker classes that implement one frontend (i.e.
Definition Checker.h:553
IntrusiveRefCntPtr< const ProgramState > ProgramStateRef
std::variant< struct RequiresDecl, struct HeaderDecl, struct UmbrellaDirDecl, struct ModuleDecl, struct ExcludeDecl, struct ExportDecl, struct ExportAsDecl, struct ExternModuleDecl, struct UseDecl, struct LinkDecl, struct ConfigMacrosDecl, struct ConflictDecl > Decl
All declarations that can appear in a module declaration.
The JSON file list parser is used to communicate input to InstallAPI.