clang 22.0.0git
StdVariantChecker.cpp
Go to the documentation of this file.
1//===- StdVariantChecker.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#include "clang/AST/Type.h"
18#include "llvm/ADT/FoldingSet.h"
19#include "llvm/ADT/StringRef.h"
20#include <optional>
21
22#include "TaggedUnionModeling.h"
23
24using namespace clang;
25using namespace ento;
26using namespace tagged_union_modeling;
27
28REGISTER_MAP_WITH_PROGRAMSTATE(VariantHeldTypeMap, const MemRegion *, QualType)
29
31
32static const CXXConstructorDecl *
34 const auto *ConstructorCall = dyn_cast<CXXConstructorCall>(&Call);
35 if (!ConstructorCall)
36 return nullptr;
37
38 return ConstructorCall->getDecl();
39}
40
42 if (const CXXConstructorDecl *ConstructorDecl =
44 return ConstructorDecl->isCopyConstructor();
45 return false;
46}
47
49 const Decl *CopyAssignmentDecl = Call.getDecl();
50
51 if (const auto *AsMethodDecl =
52 dyn_cast_or_null<CXXMethodDecl>(CopyAssignmentDecl))
53 return AsMethodDecl->isCopyAssignmentOperator();
54 return false;
55}
56
58 const CXXConstructorDecl *ConstructorDecl =
60 if (!ConstructorDecl)
61 return false;
62
63 return ConstructorDecl->isMoveConstructor();
64}
65
67 const Decl *CopyAssignmentDecl = Call.getDecl();
68
69 const auto *AsMethodDecl =
70 dyn_cast_or_null<CXXMethodDecl>(CopyAssignmentDecl);
71 if (!AsMethodDecl)
72 return false;
73
74 return AsMethodDecl->isMoveAssignmentOperator();
75}
76
77static bool isStdType(const Type *Type, llvm::StringRef TypeName) {
78 auto *Decl = Type->getAsRecordDecl();
79 if (!Decl)
80 return false;
81 return (Decl->getName() == TypeName) && Decl->isInStdNamespace();
82}
83
84bool isStdVariant(const Type *Type) {
85 return isStdType(Type, llvm::StringLiteral("variant"));
86}
87
88} // end of namespace clang::ento::tagged_union_modeling
89
90static std::optional<ArrayRef<TemplateArgument>>
91getTemplateArgsFromVariant(const Type *VariantType) {
92 const auto *TempSpecType = VariantType->getAs<TemplateSpecializationType>();
93 while (TempSpecType && TempSpecType->isTypeAlias())
94 TempSpecType =
95 TempSpecType->getAliasedType()->getAs<TemplateSpecializationType>();
96 if (!TempSpecType)
97 return {};
98
99 return TempSpecType->template_arguments();
100}
101
102static std::optional<QualType>
103getNthTemplateTypeArgFromVariant(const Type *varType, unsigned i) {
104 std::optional<ArrayRef<TemplateArgument>> VariantTemplates =
106 if (!VariantTemplates)
107 return {};
108
109 return (*VariantTemplates)[i].getAsType();
110}
111
112static bool isVowel(char a) {
113 switch (a) {
114 case 'a':
115 case 'e':
116 case 'i':
117 case 'o':
118 case 'u':
119 return true;
120 default:
121 return false;
122 }
123}
124
125static llvm::StringRef indefiniteArticleBasedOnVowel(char a) {
126 if (isVowel(a))
127 return "an";
128 return "a";
129}
130
131class StdVariantChecker : public Checker<eval::Call, check::RegionChanges> {
132 // Call descriptors to find relevant calls
133 CallDescription VariantConstructor{CDM::CXXMethod,
134 {"std", "variant", "variant"}};
135 CallDescription VariantAssignmentOperator{CDM::CXXMethod,
136 {"std", "variant", "operator="}};
137 CallDescription StdGet{CDM::SimpleFunc, {"std", "get"}, 1, 1};
138
139 BugType BadVariantType{this, "BadVariantType", "BadVariantType"};
140
141public:
143 const InvalidatedSymbols *,
146 const LocationContext *,
147 const CallEvent *Call) const {
148 if (!Call)
149 return State;
150
152 *Call, State, Regions);
153 }
154
155 bool evalCall(const CallEvent &Call, CheckerContext &C) const {
156 // Check if the call was not made from a system header. If it was then
157 // we do an early return because it is part of the implementation.
158 if (Call.isCalledFromSystemHeader())
159 return false;
160
161 if (StdGet.matches(Call))
162 return handleStdGetCall(Call, C);
163
164 // First check if a constructor call is happening. If it is a
165 // constructor call, check if it is an std::variant constructor call.
166 bool IsVariantConstructor =
167 isa<CXXConstructorCall>(Call) && VariantConstructor.matches(Call);
168 bool IsVariantAssignmentOperatorCall =
170 VariantAssignmentOperator.matches(Call);
171
172 if (IsVariantConstructor || IsVariantAssignmentOperatorCall) {
173 if (Call.getNumArgs() == 0 && IsVariantConstructor) {
174 handleDefaultConstructor(cast<CXXConstructorCall>(&Call), C);
175 return true;
176 }
177
178 // FIXME Later this checker should be extended to handle constructors
179 // with multiple arguments.
180 if (Call.getNumArgs() != 1)
181 return false;
182
183 SVal ThisSVal;
184 if (IsVariantConstructor) {
185 const auto &AsConstructorCall = cast<CXXConstructorCall>(Call);
186 ThisSVal = AsConstructorCall.getCXXThisVal();
187 } else if (IsVariantAssignmentOperatorCall) {
188 const auto &AsMemberOpCall = cast<CXXMemberOperatorCall>(Call);
189 ThisSVal = AsMemberOpCall.getCXXThisVal();
190 } else {
191 return false;
192 }
193
195 return true;
196 }
197 return false;
198 }
199
200private:
201 // The default constructed std::variant must be handled separately
202 // by default the std::variant is going to hold a default constructed instance
203 // of the first type of the possible types
204 void handleDefaultConstructor(const CXXConstructorCall *ConstructorCall,
205 CheckerContext &C) const {
206 SVal ThisSVal = ConstructorCall->getCXXThisVal();
207
208 const auto *const ThisMemRegion = ThisSVal.getAsRegion();
209 if (!ThisMemRegion)
210 return;
211
212 std::optional<QualType> DefaultType = getNthTemplateTypeArgFromVariant(
213 ThisSVal.getType(C.getASTContext())->getPointeeType().getTypePtr(), 0);
214 if (!DefaultType)
215 return;
216
217 ProgramStateRef State = C.getState();
218 State = State->set<VariantHeldTypeMap>(ThisMemRegion, *DefaultType);
219 C.addTransition(State);
220 }
221
222 bool handleStdGetCall(const CallEvent &Call, CheckerContext &C) const {
223 ProgramStateRef State = C.getState();
224
225 SVal ArgSVal = Call.getArgSVal(0);
226 if (ArgSVal.isUnknown())
227 return false;
228
229 const auto &ArgType =
230 ArgSVal.getType(C.getASTContext())->getPointeeType().getTypePtr();
231 // We have to make sure that the argument is an std::variant.
232 // There is another std::get with std::pair argument
233 if (!isStdVariant(ArgType))
234 return false;
235
236 // Get the mem region of the argument std::variant and look up the type
237 // information that we know about it.
238 const MemRegion *ArgMemRegion = Call.getArgSVal(0).getAsRegion();
239 const QualType *StoredType = State->get<VariantHeldTypeMap>(ArgMemRegion);
240 if (!StoredType)
241 return false;
242
243 const CallExpr *CE = cast<CallExpr>(Call.getOriginExpr());
244 const FunctionDecl *FD = CE->getDirectCallee();
245 if (FD->getTemplateSpecializationArgs()->size() < 1)
246 return false;
247
248 const auto &TypeOut = FD->getTemplateSpecializationArgs()->asArray()[0];
249 // std::get's first template parameter can be the type we want to get
250 // out of the std::variant or a natural number which is the position of
251 // the requested type in the argument type list of the std::variant's
252 // argument.
253 QualType RetrievedType;
254 switch (TypeOut.getKind()) {
255 case TemplateArgument::ArgKind::Type:
256 RetrievedType = TypeOut.getAsType();
257 break;
258 case TemplateArgument::ArgKind::Integral:
259 // In the natural number case we look up which type corresponds to the
260 // number.
261 if (std::optional<QualType> NthTemplate =
263 ArgType, TypeOut.getAsIntegral().getSExtValue())) {
264 RetrievedType = *NthTemplate;
265 break;
266 }
267 [[fallthrough]];
268 default:
269 return false;
270 }
271
272 QualType RetrievedCanonicalType = RetrievedType.getCanonicalType();
273 QualType StoredCanonicalType = StoredType->getCanonicalType();
274 if (RetrievedCanonicalType == StoredCanonicalType)
275 return true;
276
277 ExplodedNode *ErrNode = C.generateNonFatalErrorNode();
278 if (!ErrNode)
279 return false;
280 llvm::SmallString<128> Str;
281 llvm::raw_svector_ostream OS(Str);
282 std::string StoredTypeName = StoredType->getAsString();
283 std::string RetrievedTypeName = RetrievedType.getAsString();
284 OS << "std::variant " << ArgMemRegion->getDescriptiveName() << " held "
285 << indefiniteArticleBasedOnVowel(StoredTypeName[0]) << " \'"
286 << StoredTypeName << "\', not "
287 << indefiniteArticleBasedOnVowel(RetrievedTypeName[0]) << " \'"
288 << RetrievedTypeName << "\'";
289 auto R = std::make_unique<PathSensitiveBugReport>(BadVariantType, OS.str(),
290 ErrNode);
291 C.emitReport(std::move(R));
292 return true;
293 }
294};
295
296bool clang::ento::shouldRegisterStdVariantChecker(
297 clang::ento::CheckerManager const &mgr) {
298 return true;
299}
300
301void clang::ento::registerStdVariantChecker(clang::ento::CheckerManager &mgr) {
303}
#define REGISTER_MAP_WITH_PROGRAMSTATE(Name, Key, Value)
Declares an immutable map of type NameTy, suitable for placement into the ProgramState.
static std::optional< ArrayRef< TemplateArgument > > getTemplateArgsFromVariant(const Type *VariantType)
static std::optional< QualType > getNthTemplateTypeArgFromVariant(const Type *varType, unsigned i)
static llvm::StringRef indefiniteArticleBasedOnVowel(char a)
static bool isVowel(char a)
C Language Family Type Representation.
ProgramStateRef checkRegionChanges(ProgramStateRef State, const InvalidatedSymbols *, ArrayRef< const MemRegion * >, ArrayRef< const MemRegion * > Regions, const LocationContext *, const CallEvent *Call) const
bool evalCall(const CallEvent &Call, CheckerContext &C) const
Represents a C++ constructor within a class.
Definition DeclCXX.h:2604
bool isMoveConstructor(unsigned &TypeQuals) const
Determine whether this constructor is a move constructor (C++11 [class.copy]p3), which can be used to...
Definition DeclCXX.cpp:3013
FunctionDecl * getDirectCallee()
If the callee is a FunctionDecl, return it. Otherwise return null.
Definition Expr.h:3060
Decl - This represents one declaration (or definition), e.g.
Definition DeclBase.h:86
bool isInStdNamespace() const
Definition DeclBase.cpp:449
const TemplateArgumentList * getTemplateSpecializationArgs() const
Retrieve the template arguments used to produce this function template specialization from the primar...
Definition Decl.cpp:4323
It wraps the AnalysisDeclContext to represent both the call stack with the help of StackFrameContext ...
A (possibly-)qualified type.
Definition TypeBase.h:937
const Type * getTypePtr() const
Retrieves a pointer to the underlying (unqualified) type.
Definition TypeBase.h:8278
QualType getCanonicalType() const
Definition TypeBase.h:8330
static std::string getAsString(SplitQualType split, const PrintingPolicy &Policy)
Definition TypeBase.h:1332
unsigned size() const
Retrieve the number of template arguments in this template argument list.
ArrayRef< TemplateArgument > asArray() const
Produce this as an array ref.
The base class of the type hierarchy.
Definition TypeBase.h:1833
RecordDecl * getAsRecordDecl() const
Retrieves the RecordDecl this type refers to.
Definition Type.h:41
QualType getPointeeType() const
If this is a pointer, ObjC object pointer, or block pointer, this returns the respective pointee.
Definition Type.cpp:752
const T * getAs() const
Member-template getAs<specific type>'.
Definition TypeBase.h:9091
SVal getCXXThisVal() const
Returns the value of the implicit 'this' object.
Represents a call to a C++ constructor.
Definition CallEvent.h:997
A CallDescription is a pattern that can be used to match calls based on the qualified name and the ar...
Represents an abstract call to a function or method along a particular path.
Definition CallEvent.h:153
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
MemRegion - The root abstract class for all memory regions.
Definition MemRegion.h:98
std::string getDescriptiveName(bool UseQuotes=true) const
Get descriptive name for memory region.
SVal - This represents a symbolic expression, which can be either an L-value or an R-value.
Definition SVals.h:56
QualType getType(const ASTContext &) const
Try to get a reasonable type for the given value.
Definition SVals.cpp:180
const MemRegion * getAsRegion() const
Definition SVals.cpp:119
bool isUnknown() const
Definition SVals.h:105
ProgramStateRef removeInformationStoredForDeadInstances(const CallEvent &Call, ProgramStateRef State, ArrayRef< const MemRegion * > Regions)
bool isCopyAssignmentCall(const CallEvent &Call)
static const CXXConstructorDecl * getConstructorDeclarationForCall(const CallEvent &Call)
static bool isStdType(const Type *Type, llvm::StringRef TypeName)
bool isMoveAssignmentCall(const CallEvent &Call)
bool isCopyConstructorCall(const CallEvent &Call)
void handleConstructorAndAssignment(const CallEvent &Call, CheckerContext &C, SVal ThisSVal)
bool isMoveConstructorCall(const CallEvent &Call)
llvm::DenseSet< SymbolRef > InvalidatedSymbols
Definition Store.h:51
IntrusiveRefCntPtr< const ProgramState > ProgramStateRef
@ OS
Indicates that the tracking object is a descendant of a referenced-counted OSObject,...
The JSON file list parser is used to communicate input to InstallAPI.
bool isa(CodeGen::Address addr)
Definition Address.h:330
U cast(CodeGen::Address addr)
Definition Address.h:327