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 * 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 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 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 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 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 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 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>> 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> 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 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 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: 139*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 152*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 201*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 219*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 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 296*5f757f3fSDimitry Andric void clang::ento::registerStdVariantChecker(clang::ento::CheckerManager &mgr) { 297*5f757f3fSDimitry Andric mgr.registerChecker<StdVariantChecker>(); 298*5f757f3fSDimitry Andric }