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