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 }