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"
10#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
11#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
12#include "clang/StaticAnalyzer/Core/Checker.h"
13#include "clang/StaticAnalyzer/Core/CheckerManager.h"
14#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
15#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
16#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
17#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"
18#include "llvm/ADT/StringRef.h"
19#include <optional>
20
21#include "TaggedUnionModeling.h"
22
23using namespace clang;
24using namespace ento;
25using namespace tagged_union_modeling;
26
27REGISTER_MAP_WITH_PROGRAMSTATE(VariantHeldTypeMap, const MemRegion *, QualType)
28
29namespace clang::ento::tagged_union_modeling {
30
31static const CXXConstructorDecl *
32getConstructorDeclarationForCall(const CallEvent &Call) {
33 const auto *ConstructorCall = dyn_cast<CXXConstructorCall>(Val: &Call);
34 if (!ConstructorCall)
35 return nullptr;
36
37 return ConstructorCall->getDecl();
38}
39
40bool isCopyConstructorCall(const CallEvent &Call) {
41 if (const CXXConstructorDecl *ConstructorDecl =
42 getConstructorDeclarationForCall(Call))
43 return ConstructorDecl->isCopyConstructor();
44 return false;
45}
46
47bool isCopyAssignmentCall(const CallEvent &Call) {
48 const Decl *CopyAssignmentDecl = Call.getDecl();
49
50 if (const auto *AsMethodDecl =
51 dyn_cast_or_null<CXXMethodDecl>(Val: CopyAssignmentDecl))
52 return AsMethodDecl->isCopyAssignmentOperator();
53 return false;
54}
55
56bool isMoveConstructorCall(const CallEvent &Call) {
57 const CXXConstructorDecl *ConstructorDecl =
58 getConstructorDeclarationForCall(Call);
59 if (!ConstructorDecl)
60 return false;
61
62 return ConstructorDecl->isMoveConstructor();
63}
64
65bool isMoveAssignmentCall(const CallEvent &Call) {
66 const Decl *CopyAssignmentDecl = Call.getDecl();
67
68 const auto *AsMethodDecl =
69 dyn_cast_or_null<CXXMethodDecl>(Val: CopyAssignmentDecl);
70 if (!AsMethodDecl)
71 return false;
72
73 return AsMethodDecl->isMoveAssignmentOperator();
74}
75
76static bool isStdType(const Type *Type, llvm::StringRef TypeName) {
77 auto *Decl = Type->getAsRecordDecl();
78 if (!Decl)
79 return false;
80 return (Decl->getName() == TypeName) && Decl->isInStdNamespace();
81}
82
83bool isStdVariant(const Type *Type) {
84 return isStdType(Type, TypeName: llvm::StringLiteral("variant"));
85}
86
87} // end of namespace clang::ento::tagged_union_modeling
88
89static std::optional<ArrayRef<TemplateArgument>>
90getTemplateArgsFromVariant(const Type *VariantType) {
91 const auto *TempSpecType = VariantType->getAs<TemplateSpecializationType>();
92 while (TempSpecType && TempSpecType->isTypeAlias())
93 TempSpecType =
94 TempSpecType->getAliasedType()->getAs<TemplateSpecializationType>();
95 if (!TempSpecType)
96 return {};
97
98 return TempSpecType->template_arguments();
99}
100
101static std::optional<QualType>
102getNthTemplateTypeArgFromVariant(const Type *varType, unsigned i) {
103 std::optional<ArrayRef<TemplateArgument>> VariantTemplates =
104 getTemplateArgsFromVariant(VariantType: varType);
105 if (!VariantTemplates)
106 return {};
107
108 return (*VariantTemplates)[i].getAsType();
109}
110
111static bool isVowel(char a) {
112 switch (a) {
113 case 'a':
114 case 'e':
115 case 'i':
116 case 'o':
117 case 'u':
118 return true;
119 default:
120 return false;
121 }
122}
123
124static llvm::StringRef indefiniteArticleBasedOnVowel(char a) {
125 if (isVowel(a))
126 return "an";
127 return "a";
128}
129
130class StdVariantChecker : public Checker<eval::Call, check::RegionChanges> {
131 // Call descriptors to find relevant calls
132 CallDescription VariantConstructor{CDM::CXXMethod,
133 {"std", "variant", "variant"}};
134 CallDescription VariantAssignmentOperator{CDM::CXXMethod,
135 {"std", "variant", "operator="}};
136 CallDescription StdGet{CDM::SimpleFunc, {"std", "get"}, 1, 1};
137
138 BugType BadVariantType{this, "BadVariantType", "BadVariantType"};
139
140public:
141 ProgramStateRef checkRegionChanges(ProgramStateRef State,
142 const InvalidatedSymbols *,
143 ArrayRef<const MemRegion *>,
144 ArrayRef<const MemRegion *> Regions,
145 const StackFrame *,
146 const CallEvent *Call) const {
147 if (!Call)
148 return State;
149
150 return removeInformationStoredForDeadInstances<VariantHeldTypeMap>(
151 Call: *Call, State, Regions);
152 }
153
154 bool evalCall(const CallEvent &Call, CheckerContext &C) const {
155 // Check if the call was not made from a system header. If it was then
156 // we do an early return because it is part of the implementation.
157 if (Call.isCalledFromSystemHeader())
158 return false;
159
160 if (StdGet.matches(Call))
161 return handleStdGetCall(Call, C);
162
163 // First check if a constructor call is happening. If it is a
164 // constructor call, check if it is an std::variant constructor call.
165 bool IsVariantConstructor =
166 isa<CXXConstructorCall>(Val: Call) && VariantConstructor.matches(Call);
167 bool IsVariantAssignmentOperatorCall =
168 isa<CXXMemberOperatorCall>(Val: Call) &&
169 VariantAssignmentOperator.matches(Call);
170
171 if (IsVariantConstructor || IsVariantAssignmentOperatorCall) {
172 if (Call.getNumArgs() == 0 && IsVariantConstructor) {
173 handleDefaultConstructor(ConstructorCall: cast<CXXConstructorCall>(Val: &Call), C);
174 return true;
175 }
176
177 // FIXME Later this checker should be extended to handle constructors
178 // with multiple arguments.
179 if (Call.getNumArgs() != 1)
180 return false;
181
182 SVal ThisSVal;
183 if (IsVariantConstructor) {
184 const auto &AsConstructorCall = cast<CXXConstructorCall>(Val: Call);
185 ThisSVal = AsConstructorCall.getCXXThisVal();
186 } else if (IsVariantAssignmentOperatorCall) {
187 const auto &AsMemberOpCall = cast<CXXMemberOperatorCall>(Val: Call);
188 ThisSVal = AsMemberOpCall.getCXXThisVal();
189 } else {
190 return false;
191 }
192
193 handleConstructorAndAssignment<VariantHeldTypeMap>(Call, C, ThisSVal);
194 return true;
195 }
196 return false;
197 }
198
199private:
200 // The default constructed std::variant must be handled separately
201 // by default the std::variant is going to hold a default constructed instance
202 // of the first type of the possible types
203 void handleDefaultConstructor(const CXXConstructorCall *ConstructorCall,
204 CheckerContext &C) const {
205 SVal ThisSVal = ConstructorCall->getCXXThisVal();
206
207 const auto *const ThisMemRegion = ThisSVal.getAsRegion();
208 if (!ThisMemRegion)
209 return;
210
211 std::optional<QualType> DefaultType = getNthTemplateTypeArgFromVariant(
212 varType: ThisSVal.getType(C.getASTContext())->getPointeeType().getTypePtr(), i: 0);
213 if (!DefaultType)
214 return;
215
216 ProgramStateRef State = C.getState();
217 State = State->set<VariantHeldTypeMap>(K: ThisMemRegion, E: *DefaultType);
218 C.addTransition(State);
219 }
220
221 bool handleStdGetCall(const CallEvent &Call, CheckerContext &C) const {
222 ProgramStateRef State = C.getState();
223
224 const auto *ArgType = Call.getArgExpr(Index: 0)->getType().getTypePtr();
225 // We have to make sure that the argument is an std::variant.
226 // There is another std::get with std::pair argument
227 if (!isStdVariant(Type: ArgType))
228 return false;
229
230 // Get the mem region of the argument std::variant and look up the type
231 // information that we know about it.
232 const MemRegion *ArgMemRegion = Call.getArgSVal(Index: 0).getAsRegion();
233 const QualType *StoredType = State->get<VariantHeldTypeMap>(key: ArgMemRegion);
234 if (!StoredType)
235 return false;
236
237 const CallExpr *CE = cast<CallExpr>(Val: Call.getOriginExpr());
238 const FunctionDecl *FD = CE->getDirectCallee();
239 if (FD->getTemplateSpecializationArgs()->size() < 1)
240 return false;
241
242 const auto &TypeOut = FD->getTemplateSpecializationArgs()->asArray()[0];
243 // std::get's first template parameter can be the type we want to get
244 // out of the std::variant or a natural number which is the position of
245 // the requested type in the argument type list of the std::variant's
246 // argument.
247 QualType RetrievedType;
248 switch (TypeOut.getKind()) {
249 case TemplateArgument::ArgKind::Type:
250 RetrievedType = TypeOut.getAsType();
251 break;
252 case TemplateArgument::ArgKind::Integral:
253 // In the natural number case we look up which type corresponds to the
254 // number.
255 if (std::optional<QualType> NthTemplate =
256 getNthTemplateTypeArgFromVariant(
257 varType: ArgType, i: TypeOut.getAsIntegral().getSExtValue())) {
258 RetrievedType = *NthTemplate;
259 break;
260 }
261 [[fallthrough]];
262 default:
263 return false;
264 }
265
266 QualType RetrievedCanonicalType = RetrievedType.getCanonicalType();
267 QualType StoredCanonicalType = StoredType->getCanonicalType();
268 if (RetrievedCanonicalType == StoredCanonicalType)
269 return true;
270
271 ExplodedNode *ErrNode = C.generateNonFatalErrorNode();
272 if (!ErrNode)
273 return false;
274 llvm::SmallString<128> Str;
275 llvm::raw_svector_ostream OS(Str);
276 std::string StoredTypeName = StoredType->getAsString();
277 std::string RetrievedTypeName = RetrievedType.getAsString();
278 OS << "std::variant " << ArgMemRegion->getDescriptiveName() << " held "
279 << indefiniteArticleBasedOnVowel(a: StoredTypeName[0]) << " \'"
280 << StoredTypeName << "\', not "
281 << indefiniteArticleBasedOnVowel(a: RetrievedTypeName[0]) << " \'"
282 << RetrievedTypeName << "\'";
283 auto R = std::make_unique<PathSensitiveBugReport>(args: BadVariantType, args: OS.str(),
284 args&: ErrNode);
285 C.emitReport(R: std::move(R));
286 return true;
287 }
288};
289
290bool clang::ento::shouldRegisterStdVariantChecker(
291 clang::ento::CheckerManager const &mgr) {
292 return true;
293}
294
295void clang::ento::registerStdVariantChecker(clang::ento::CheckerManager &mgr) {
296 mgr.registerChecker<StdVariantChecker>();
297}
298