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