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