xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp (revision b3470c3d7ab078b201bd65afc3b902d1ed41bc21)
1527fcb8eSGábor Spaits //===- StdVariantChecker.cpp -------------------------------------*- C++ -*-==//
2527fcb8eSGábor Spaits //
3527fcb8eSGábor Spaits // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4527fcb8eSGábor Spaits // See https://llvm.org/LICENSE.txt for license information.
5527fcb8eSGábor Spaits // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6527fcb8eSGábor Spaits //
7527fcb8eSGábor Spaits //===----------------------------------------------------------------------===//
8527fcb8eSGábor Spaits 
9527fcb8eSGábor Spaits #include "clang/AST/Type.h"
10527fcb8eSGábor Spaits #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
11527fcb8eSGábor Spaits #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
12527fcb8eSGábor Spaits #include "clang/StaticAnalyzer/Core/Checker.h"
13527fcb8eSGábor Spaits #include "clang/StaticAnalyzer/Core/CheckerManager.h"
14527fcb8eSGábor Spaits #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
15527fcb8eSGábor Spaits #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
16527fcb8eSGábor Spaits #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
17527fcb8eSGábor Spaits #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"
18527fcb8eSGábor Spaits #include "llvm/ADT/FoldingSet.h"
19527fcb8eSGábor Spaits #include "llvm/ADT/StringRef.h"
20527fcb8eSGábor Spaits #include "llvm/Support/Casting.h"
21527fcb8eSGábor Spaits #include <optional>
22527fcb8eSGábor Spaits #include <string_view>
23527fcb8eSGábor Spaits 
24527fcb8eSGábor Spaits #include "TaggedUnionModeling.h"
25527fcb8eSGábor Spaits 
26527fcb8eSGábor Spaits using namespace clang;
27527fcb8eSGábor Spaits using namespace ento;
28527fcb8eSGábor Spaits using namespace tagged_union_modeling;
29527fcb8eSGábor Spaits 
30527fcb8eSGábor Spaits REGISTER_MAP_WITH_PROGRAMSTATE(VariantHeldTypeMap, const MemRegion *, QualType)
31527fcb8eSGábor Spaits 
32527fcb8eSGábor Spaits namespace clang::ento::tagged_union_modeling {
33527fcb8eSGábor Spaits 
34*b3470c3dSCongcong Cai static const CXXConstructorDecl *
35527fcb8eSGábor Spaits getConstructorDeclarationForCall(const CallEvent &Call) {
36527fcb8eSGábor Spaits   const auto *ConstructorCall = dyn_cast<CXXConstructorCall>(&Call);
37527fcb8eSGábor Spaits   if (!ConstructorCall)
38527fcb8eSGábor Spaits     return nullptr;
39527fcb8eSGábor Spaits 
40527fcb8eSGábor Spaits   return ConstructorCall->getDecl();
41527fcb8eSGábor Spaits }
42527fcb8eSGábor Spaits 
43527fcb8eSGábor Spaits bool isCopyConstructorCall(const CallEvent &Call) {
44527fcb8eSGábor Spaits   if (const CXXConstructorDecl *ConstructorDecl =
45527fcb8eSGábor Spaits           getConstructorDeclarationForCall(Call))
46527fcb8eSGábor Spaits     return ConstructorDecl->isCopyConstructor();
47527fcb8eSGábor Spaits   return false;
48527fcb8eSGábor Spaits }
49527fcb8eSGábor Spaits 
50527fcb8eSGábor Spaits bool isCopyAssignmentCall(const CallEvent &Call) {
51527fcb8eSGábor Spaits   const Decl *CopyAssignmentDecl = Call.getDecl();
52527fcb8eSGábor Spaits 
53527fcb8eSGábor Spaits   if (const auto *AsMethodDecl =
54527fcb8eSGábor Spaits           dyn_cast_or_null<CXXMethodDecl>(CopyAssignmentDecl))
55527fcb8eSGábor Spaits     return AsMethodDecl->isCopyAssignmentOperator();
56527fcb8eSGábor Spaits   return false;
57527fcb8eSGábor Spaits }
58527fcb8eSGábor Spaits 
59527fcb8eSGábor Spaits bool isMoveConstructorCall(const CallEvent &Call) {
60527fcb8eSGábor Spaits   const CXXConstructorDecl *ConstructorDecl =
61527fcb8eSGábor Spaits       getConstructorDeclarationForCall(Call);
62527fcb8eSGábor Spaits   if (!ConstructorDecl)
63527fcb8eSGábor Spaits     return false;
64527fcb8eSGábor Spaits 
65527fcb8eSGábor Spaits   return ConstructorDecl->isMoveConstructor();
66527fcb8eSGábor Spaits }
67527fcb8eSGábor Spaits 
68527fcb8eSGábor Spaits bool isMoveAssignmentCall(const CallEvent &Call) {
69527fcb8eSGábor Spaits   const Decl *CopyAssignmentDecl = Call.getDecl();
70527fcb8eSGábor Spaits 
71527fcb8eSGábor Spaits   const auto *AsMethodDecl =
72527fcb8eSGábor Spaits       dyn_cast_or_null<CXXMethodDecl>(CopyAssignmentDecl);
73527fcb8eSGábor Spaits   if (!AsMethodDecl)
74527fcb8eSGábor Spaits     return false;
75527fcb8eSGábor Spaits 
76527fcb8eSGábor Spaits   return AsMethodDecl->isMoveAssignmentOperator();
77527fcb8eSGábor Spaits }
78527fcb8eSGábor Spaits 
79*b3470c3dSCongcong Cai static bool isStdType(const Type *Type, llvm::StringRef TypeName) {
80527fcb8eSGábor Spaits   auto *Decl = Type->getAsRecordDecl();
81527fcb8eSGábor Spaits   if (!Decl)
82527fcb8eSGábor Spaits     return false;
83527fcb8eSGábor Spaits   return (Decl->getName() == TypeName) && Decl->isInStdNamespace();
84527fcb8eSGábor Spaits }
85527fcb8eSGábor Spaits 
86527fcb8eSGábor Spaits bool isStdVariant(const Type *Type) {
87527fcb8eSGábor Spaits   return isStdType(Type, llvm::StringLiteral("variant"));
88527fcb8eSGábor Spaits }
89527fcb8eSGábor Spaits 
90527fcb8eSGábor Spaits } // end of namespace clang::ento::tagged_union_modeling
91527fcb8eSGábor Spaits 
92527fcb8eSGábor Spaits static std::optional<ArrayRef<TemplateArgument>>
93527fcb8eSGábor Spaits getTemplateArgsFromVariant(const Type *VariantType) {
94527fcb8eSGábor Spaits   const auto *TempSpecType = VariantType->getAs<TemplateSpecializationType>();
95527fcb8eSGábor Spaits   if (!TempSpecType)
96527fcb8eSGábor Spaits     return {};
97527fcb8eSGábor Spaits 
98527fcb8eSGábor Spaits   return TempSpecType->template_arguments();
99527fcb8eSGábor Spaits }
100527fcb8eSGábor Spaits 
101527fcb8eSGábor Spaits static std::optional<QualType>
102527fcb8eSGábor Spaits getNthTemplateTypeArgFromVariant(const Type *varType, unsigned i) {
103527fcb8eSGábor Spaits   std::optional<ArrayRef<TemplateArgument>> VariantTemplates =
104527fcb8eSGábor Spaits       getTemplateArgsFromVariant(varType);
105527fcb8eSGábor Spaits   if (!VariantTemplates)
106527fcb8eSGábor Spaits     return {};
107527fcb8eSGábor Spaits 
108527fcb8eSGábor Spaits   return (*VariantTemplates)[i].getAsType();
109527fcb8eSGábor Spaits }
110527fcb8eSGábor Spaits 
111527fcb8eSGábor Spaits static bool isVowel(char a) {
112527fcb8eSGábor Spaits   switch (a) {
113527fcb8eSGábor Spaits   case 'a':
114527fcb8eSGábor Spaits   case 'e':
115527fcb8eSGábor Spaits   case 'i':
116527fcb8eSGábor Spaits   case 'o':
117527fcb8eSGábor Spaits   case 'u':
118527fcb8eSGábor Spaits     return true;
119527fcb8eSGábor Spaits   default:
120527fcb8eSGábor Spaits     return false;
121527fcb8eSGábor Spaits   }
122527fcb8eSGábor Spaits }
123527fcb8eSGábor Spaits 
124527fcb8eSGábor Spaits static llvm::StringRef indefiniteArticleBasedOnVowel(char a) {
125527fcb8eSGábor Spaits   if (isVowel(a))
126527fcb8eSGábor Spaits     return "an";
127527fcb8eSGábor Spaits   return "a";
128527fcb8eSGábor Spaits }
129527fcb8eSGábor Spaits 
130527fcb8eSGábor Spaits class StdVariantChecker : public Checker<eval::Call, check::RegionChanges> {
131527fcb8eSGábor Spaits   // Call descriptors to find relevant calls
132e2f1cbaeSNagyDonat   CallDescription VariantConstructor{CDM::CXXMethod,
133e2f1cbaeSNagyDonat                                      {"std", "variant", "variant"}};
134e2f1cbaeSNagyDonat   CallDescription VariantAssignmentOperator{CDM::CXXMethod,
135e2f1cbaeSNagyDonat                                             {"std", "variant", "operator="}};
136e2f1cbaeSNagyDonat   CallDescription StdGet{CDM::SimpleFunc, {"std", "get"}, 1, 1};
137527fcb8eSGábor Spaits 
138527fcb8eSGábor Spaits   BugType BadVariantType{this, "BadVariantType", "BadVariantType"};
139527fcb8eSGábor Spaits 
140527fcb8eSGábor Spaits public:
141527fcb8eSGábor Spaits   ProgramStateRef checkRegionChanges(ProgramStateRef State,
142527fcb8eSGábor Spaits                                      const InvalidatedSymbols *,
143527fcb8eSGábor Spaits                                      ArrayRef<const MemRegion *>,
144527fcb8eSGábor Spaits                                      ArrayRef<const MemRegion *> Regions,
145527fcb8eSGábor Spaits                                      const LocationContext *,
146527fcb8eSGábor Spaits                                      const CallEvent *Call) const {
147527fcb8eSGábor Spaits     if (!Call)
148527fcb8eSGábor Spaits       return State;
149527fcb8eSGábor Spaits 
150527fcb8eSGábor Spaits     return removeInformationStoredForDeadInstances<VariantHeldTypeMap>(
151527fcb8eSGábor Spaits         *Call, State, Regions);
152527fcb8eSGábor Spaits   }
153527fcb8eSGábor Spaits 
154527fcb8eSGábor Spaits   bool evalCall(const CallEvent &Call, CheckerContext &C) const {
155527fcb8eSGábor Spaits     // Check if the call was not made from a system header. If it was then
156527fcb8eSGábor Spaits     // we do an early return because it is part of the implementation.
157527fcb8eSGábor Spaits     if (Call.isCalledFromSystemHeader())
158527fcb8eSGábor Spaits       return false;
159527fcb8eSGábor Spaits 
160527fcb8eSGábor Spaits     if (StdGet.matches(Call))
161527fcb8eSGábor Spaits       return handleStdGetCall(Call, C);
162527fcb8eSGábor Spaits 
163527fcb8eSGábor Spaits     // First check if a constructor call is happening. If it is a
164527fcb8eSGábor Spaits     // constructor call, check if it is an std::variant constructor call.
165527fcb8eSGábor Spaits     bool IsVariantConstructor =
166527fcb8eSGábor Spaits         isa<CXXConstructorCall>(Call) && VariantConstructor.matches(Call);
167527fcb8eSGábor Spaits     bool IsVariantAssignmentOperatorCall =
168527fcb8eSGábor Spaits         isa<CXXMemberOperatorCall>(Call) &&
169527fcb8eSGábor Spaits         VariantAssignmentOperator.matches(Call);
170527fcb8eSGábor Spaits 
171527fcb8eSGábor Spaits     if (IsVariantConstructor || IsVariantAssignmentOperatorCall) {
172527fcb8eSGábor Spaits       if (Call.getNumArgs() == 0 && IsVariantConstructor) {
173527fcb8eSGábor Spaits         handleDefaultConstructor(cast<CXXConstructorCall>(&Call), C);
174527fcb8eSGábor Spaits         return true;
175527fcb8eSGábor Spaits       }
176527fcb8eSGábor Spaits 
177527fcb8eSGábor Spaits       // FIXME Later this checker should be extended to handle constructors
178527fcb8eSGábor Spaits       // with multiple arguments.
179527fcb8eSGábor Spaits       if (Call.getNumArgs() != 1)
180527fcb8eSGábor Spaits         return false;
181527fcb8eSGábor Spaits 
182527fcb8eSGábor Spaits       SVal ThisSVal;
183527fcb8eSGábor Spaits       if (IsVariantConstructor) {
184527fcb8eSGábor Spaits         const auto &AsConstructorCall = cast<CXXConstructorCall>(Call);
185527fcb8eSGábor Spaits         ThisSVal = AsConstructorCall.getCXXThisVal();
186527fcb8eSGábor Spaits       } else if (IsVariantAssignmentOperatorCall) {
187527fcb8eSGábor Spaits         const auto &AsMemberOpCall = cast<CXXMemberOperatorCall>(Call);
188527fcb8eSGábor Spaits         ThisSVal = AsMemberOpCall.getCXXThisVal();
189527fcb8eSGábor Spaits       } else {
190527fcb8eSGábor Spaits         return false;
191527fcb8eSGábor Spaits       }
192527fcb8eSGábor Spaits 
193527fcb8eSGábor Spaits       handleConstructorAndAssignment<VariantHeldTypeMap>(Call, C, ThisSVal);
194527fcb8eSGábor Spaits       return true;
195527fcb8eSGábor Spaits     }
196527fcb8eSGábor Spaits     return false;
197527fcb8eSGábor Spaits   }
198527fcb8eSGábor Spaits 
199527fcb8eSGábor Spaits private:
200527fcb8eSGábor Spaits   // The default constructed std::variant must be handled separately
201527fcb8eSGábor Spaits   // by default the std::variant is going to hold a default constructed instance
202527fcb8eSGábor Spaits   // of the first type of the possible types
203527fcb8eSGábor Spaits   void handleDefaultConstructor(const CXXConstructorCall *ConstructorCall,
204527fcb8eSGábor Spaits                                 CheckerContext &C) const {
205527fcb8eSGábor Spaits     SVal ThisSVal = ConstructorCall->getCXXThisVal();
206527fcb8eSGábor Spaits 
207527fcb8eSGábor Spaits     const auto *const ThisMemRegion = ThisSVal.getAsRegion();
208527fcb8eSGábor Spaits     if (!ThisMemRegion)
209527fcb8eSGábor Spaits       return;
210527fcb8eSGábor Spaits 
211527fcb8eSGábor Spaits     std::optional<QualType> DefaultType = getNthTemplateTypeArgFromVariant(
212527fcb8eSGábor Spaits         ThisSVal.getType(C.getASTContext())->getPointeeType().getTypePtr(), 0);
213527fcb8eSGábor Spaits     if (!DefaultType)
214527fcb8eSGábor Spaits       return;
215527fcb8eSGábor Spaits 
216527fcb8eSGábor Spaits     ProgramStateRef State = ConstructorCall->getState();
217527fcb8eSGábor Spaits     State = State->set<VariantHeldTypeMap>(ThisMemRegion, *DefaultType);
218527fcb8eSGábor Spaits     C.addTransition(State);
219527fcb8eSGábor Spaits   }
220527fcb8eSGábor Spaits 
221527fcb8eSGábor Spaits   bool handleStdGetCall(const CallEvent &Call, CheckerContext &C) const {
222527fcb8eSGábor Spaits     ProgramStateRef State = Call.getState();
223527fcb8eSGábor Spaits 
224527fcb8eSGábor Spaits     const auto &ArgType = Call.getArgSVal(0)
225527fcb8eSGábor Spaits                               .getType(C.getASTContext())
226527fcb8eSGábor Spaits                               ->getPointeeType()
227527fcb8eSGábor Spaits                               .getTypePtr();
228527fcb8eSGábor Spaits     // We have to make sure that the argument is an std::variant.
229527fcb8eSGábor Spaits     // There is another std::get with std::pair argument
230527fcb8eSGábor Spaits     if (!isStdVariant(ArgType))
231527fcb8eSGábor Spaits       return false;
232527fcb8eSGábor Spaits 
233527fcb8eSGábor Spaits     // Get the mem region of the argument std::variant and look up the type
234527fcb8eSGábor Spaits     // information that we know about it.
235527fcb8eSGábor Spaits     const MemRegion *ArgMemRegion = Call.getArgSVal(0).getAsRegion();
236527fcb8eSGábor Spaits     const QualType *StoredType = State->get<VariantHeldTypeMap>(ArgMemRegion);
237527fcb8eSGábor Spaits     if (!StoredType)
238527fcb8eSGábor Spaits       return false;
239527fcb8eSGábor Spaits 
240527fcb8eSGábor Spaits     const CallExpr *CE = cast<CallExpr>(Call.getOriginExpr());
241527fcb8eSGábor Spaits     const FunctionDecl *FD = CE->getDirectCallee();
242527fcb8eSGábor Spaits     if (FD->getTemplateSpecializationArgs()->size() < 1)
243527fcb8eSGábor Spaits       return false;
244527fcb8eSGábor Spaits 
245527fcb8eSGábor Spaits     const auto &TypeOut = FD->getTemplateSpecializationArgs()->asArray()[0];
246527fcb8eSGábor Spaits     // std::get's first template parameter can be the type we want to get
247527fcb8eSGábor Spaits     // out of the std::variant or a natural number which is the position of
248527fcb8eSGábor Spaits     // the requested type in the argument type list of the std::variant's
249527fcb8eSGábor Spaits     // argument.
250527fcb8eSGábor Spaits     QualType RetrievedType;
251527fcb8eSGábor Spaits     switch (TypeOut.getKind()) {
252527fcb8eSGábor Spaits     case TemplateArgument::ArgKind::Type:
253527fcb8eSGábor Spaits       RetrievedType = TypeOut.getAsType();
254527fcb8eSGábor Spaits       break;
255527fcb8eSGábor Spaits     case TemplateArgument::ArgKind::Integral:
256527fcb8eSGábor Spaits       // In the natural number case we look up which type corresponds to the
257527fcb8eSGábor Spaits       // number.
258527fcb8eSGábor Spaits       if (std::optional<QualType> NthTemplate =
259527fcb8eSGábor Spaits               getNthTemplateTypeArgFromVariant(
260527fcb8eSGábor Spaits                   ArgType, TypeOut.getAsIntegral().getSExtValue())) {
261527fcb8eSGábor Spaits         RetrievedType = *NthTemplate;
262527fcb8eSGábor Spaits         break;
263527fcb8eSGábor Spaits       }
264527fcb8eSGábor Spaits       [[fallthrough]];
265527fcb8eSGábor Spaits     default:
266527fcb8eSGábor Spaits       return false;
267527fcb8eSGábor Spaits     }
268527fcb8eSGábor Spaits 
269527fcb8eSGábor Spaits     QualType RetrievedCanonicalType = RetrievedType.getCanonicalType();
270527fcb8eSGábor Spaits     QualType StoredCanonicalType = StoredType->getCanonicalType();
271527fcb8eSGábor Spaits     if (RetrievedCanonicalType == StoredCanonicalType)
272527fcb8eSGábor Spaits       return true;
273527fcb8eSGábor Spaits 
274527fcb8eSGábor Spaits     ExplodedNode *ErrNode = C.generateNonFatalErrorNode();
275527fcb8eSGábor Spaits     if (!ErrNode)
276527fcb8eSGábor Spaits       return false;
277527fcb8eSGábor Spaits     llvm::SmallString<128> Str;
278527fcb8eSGábor Spaits     llvm::raw_svector_ostream OS(Str);
279527fcb8eSGábor Spaits     std::string StoredTypeName = StoredType->getAsString();
280527fcb8eSGábor Spaits     std::string RetrievedTypeName = RetrievedType.getAsString();
281527fcb8eSGábor Spaits     OS << "std::variant " << ArgMemRegion->getDescriptiveName() << " held "
282527fcb8eSGábor Spaits        << indefiniteArticleBasedOnVowel(StoredTypeName[0]) << " \'"
283527fcb8eSGábor Spaits        << StoredTypeName << "\', not "
284527fcb8eSGábor Spaits        << indefiniteArticleBasedOnVowel(RetrievedTypeName[0]) << " \'"
285527fcb8eSGábor Spaits        << RetrievedTypeName << "\'";
286527fcb8eSGábor Spaits     auto R = std::make_unique<PathSensitiveBugReport>(BadVariantType, OS.str(),
287527fcb8eSGábor Spaits                                                       ErrNode);
288527fcb8eSGábor Spaits     C.emitReport(std::move(R));
289527fcb8eSGábor Spaits     return true;
290527fcb8eSGábor Spaits   }
291527fcb8eSGábor Spaits };
292527fcb8eSGábor Spaits 
293527fcb8eSGábor Spaits bool clang::ento::shouldRegisterStdVariantChecker(
294527fcb8eSGábor Spaits     clang::ento::CheckerManager const &mgr) {
295527fcb8eSGábor Spaits   return true;
296527fcb8eSGábor Spaits }
297527fcb8eSGábor Spaits 
298527fcb8eSGábor Spaits void clang::ento::registerStdVariantChecker(clang::ento::CheckerManager &mgr) {
299527fcb8eSGábor Spaits   mgr.registerChecker<StdVariantChecker>();
300527fcb8eSGábor Spaits }
301