xref: /freebsd/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp (revision e64bea71c21eb42e97aa615188ba91f6cce0d36d)
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>(&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>(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>(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, 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(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, 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>(Call) && VariantConstructor.matches(Call);
165     bool IsVariantAssignmentOperatorCall =
166         isa<CXXMemberOperatorCall>(Call) &&
167         VariantAssignmentOperator.matches(Call);
168 
169     if (IsVariantConstructor || IsVariantAssignmentOperatorCall) {
170       if (Call.getNumArgs() == 0 && IsVariantConstructor) {
171         handleDefaultConstructor(cast<CXXConstructorCall>(&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>(Call);
183         ThisSVal = AsConstructorCall.getCXXThisVal();
184       } else if (IsVariantAssignmentOperatorCall) {
185         const auto &AsMemberOpCall = cast<CXXMemberOperatorCall>(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         ThisSVal.getType(C.getASTContext())->getPointeeType().getTypePtr(), 0);
211     if (!DefaultType)
212       return;
213 
214     ProgramStateRef State = ConstructorCall->getState();
215     State = State->set<VariantHeldTypeMap>(ThisMemRegion, *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(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(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(0).getAsRegion();
234     const QualType *StoredType = State->get<VariantHeldTypeMap>(ArgMemRegion);
235     if (!StoredType)
236       return false;
237 
238     const CallExpr *CE = cast<CallExpr>(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                   ArgType, 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(StoredTypeName[0]) << " \'"
281        << StoredTypeName << "\', not "
282        << indefiniteArticleBasedOnVowel(RetrievedTypeName[0]) << " \'"
283        << RetrievedTypeName << "\'";
284     auto R = std::make_unique<PathSensitiveBugReport>(BadVariantType, OS.str(),
285                                                       ErrNode);
286     C.emitReport(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