15f757f3fSDimitry Andric //===- StdVariantChecker.cpp -------------------------------------*- C++ -*-==// 25f757f3fSDimitry Andric // 35f757f3fSDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 45f757f3fSDimitry Andric // See https://llvm.org/LICENSE.txt for license information. 55f757f3fSDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 65f757f3fSDimitry Andric // 75f757f3fSDimitry Andric //===----------------------------------------------------------------------===// 85f757f3fSDimitry Andric 95f757f3fSDimitry Andric #include "clang/AST/Type.h" 105f757f3fSDimitry Andric #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" 115f757f3fSDimitry Andric #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 125f757f3fSDimitry Andric #include "clang/StaticAnalyzer/Core/Checker.h" 135f757f3fSDimitry Andric #include "clang/StaticAnalyzer/Core/CheckerManager.h" 145f757f3fSDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" 155f757f3fSDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" 165f757f3fSDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 175f757f3fSDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" 185f757f3fSDimitry Andric #include "llvm/ADT/FoldingSet.h" 195f757f3fSDimitry Andric #include "llvm/ADT/StringRef.h" 205f757f3fSDimitry Andric #include "llvm/Support/Casting.h" 215f757f3fSDimitry Andric #include <optional> 225f757f3fSDimitry Andric #include <string_view> 235f757f3fSDimitry Andric 245f757f3fSDimitry Andric #include "TaggedUnionModeling.h" 255f757f3fSDimitry Andric 265f757f3fSDimitry Andric using namespace clang; 275f757f3fSDimitry Andric using namespace ento; 285f757f3fSDimitry Andric using namespace tagged_union_modeling; 295f757f3fSDimitry Andric 305f757f3fSDimitry Andric REGISTER_MAP_WITH_PROGRAMSTATE(VariantHeldTypeMap, const MemRegion *, QualType) 315f757f3fSDimitry Andric 325f757f3fSDimitry Andric namespace clang::ento::tagged_union_modeling { 335f757f3fSDimitry Andric 345f757f3fSDimitry Andric const CXXConstructorDecl * 355f757f3fSDimitry Andric getConstructorDeclarationForCall(const CallEvent &Call) { 365f757f3fSDimitry Andric const auto *ConstructorCall = dyn_cast<CXXConstructorCall>(&Call); 375f757f3fSDimitry Andric if (!ConstructorCall) 385f757f3fSDimitry Andric return nullptr; 395f757f3fSDimitry Andric 405f757f3fSDimitry Andric return ConstructorCall->getDecl(); 415f757f3fSDimitry Andric } 425f757f3fSDimitry Andric 435f757f3fSDimitry Andric bool isCopyConstructorCall(const CallEvent &Call) { 445f757f3fSDimitry Andric if (const CXXConstructorDecl *ConstructorDecl = 455f757f3fSDimitry Andric getConstructorDeclarationForCall(Call)) 465f757f3fSDimitry Andric return ConstructorDecl->isCopyConstructor(); 475f757f3fSDimitry Andric return false; 485f757f3fSDimitry Andric } 495f757f3fSDimitry Andric 505f757f3fSDimitry Andric bool isCopyAssignmentCall(const CallEvent &Call) { 515f757f3fSDimitry Andric const Decl *CopyAssignmentDecl = Call.getDecl(); 525f757f3fSDimitry Andric 535f757f3fSDimitry Andric if (const auto *AsMethodDecl = 545f757f3fSDimitry Andric dyn_cast_or_null<CXXMethodDecl>(CopyAssignmentDecl)) 555f757f3fSDimitry Andric return AsMethodDecl->isCopyAssignmentOperator(); 565f757f3fSDimitry Andric return false; 575f757f3fSDimitry Andric } 585f757f3fSDimitry Andric 595f757f3fSDimitry Andric bool isMoveConstructorCall(const CallEvent &Call) { 605f757f3fSDimitry Andric const CXXConstructorDecl *ConstructorDecl = 615f757f3fSDimitry Andric getConstructorDeclarationForCall(Call); 625f757f3fSDimitry Andric if (!ConstructorDecl) 635f757f3fSDimitry Andric return false; 645f757f3fSDimitry Andric 655f757f3fSDimitry Andric return ConstructorDecl->isMoveConstructor(); 665f757f3fSDimitry Andric } 675f757f3fSDimitry Andric 685f757f3fSDimitry Andric bool isMoveAssignmentCall(const CallEvent &Call) { 695f757f3fSDimitry Andric const Decl *CopyAssignmentDecl = Call.getDecl(); 705f757f3fSDimitry Andric 715f757f3fSDimitry Andric const auto *AsMethodDecl = 725f757f3fSDimitry Andric dyn_cast_or_null<CXXMethodDecl>(CopyAssignmentDecl); 735f757f3fSDimitry Andric if (!AsMethodDecl) 745f757f3fSDimitry Andric return false; 755f757f3fSDimitry Andric 765f757f3fSDimitry Andric return AsMethodDecl->isMoveAssignmentOperator(); 775f757f3fSDimitry Andric } 785f757f3fSDimitry Andric 795f757f3fSDimitry Andric bool isStdType(const Type *Type, llvm::StringRef TypeName) { 805f757f3fSDimitry Andric auto *Decl = Type->getAsRecordDecl(); 815f757f3fSDimitry Andric if (!Decl) 825f757f3fSDimitry Andric return false; 835f757f3fSDimitry Andric return (Decl->getName() == TypeName) && Decl->isInStdNamespace(); 845f757f3fSDimitry Andric } 855f757f3fSDimitry Andric 865f757f3fSDimitry Andric bool isStdVariant(const Type *Type) { 875f757f3fSDimitry Andric return isStdType(Type, llvm::StringLiteral("variant")); 885f757f3fSDimitry Andric } 895f757f3fSDimitry Andric 905f757f3fSDimitry Andric } // end of namespace clang::ento::tagged_union_modeling 915f757f3fSDimitry Andric 925f757f3fSDimitry Andric static std::optional<ArrayRef<TemplateArgument>> 935f757f3fSDimitry Andric getTemplateArgsFromVariant(const Type *VariantType) { 945f757f3fSDimitry Andric const auto *TempSpecType = VariantType->getAs<TemplateSpecializationType>(); 955f757f3fSDimitry Andric if (!TempSpecType) 965f757f3fSDimitry Andric return {}; 975f757f3fSDimitry Andric 985f757f3fSDimitry Andric return TempSpecType->template_arguments(); 995f757f3fSDimitry Andric } 1005f757f3fSDimitry Andric 1015f757f3fSDimitry Andric static std::optional<QualType> 1025f757f3fSDimitry Andric getNthTemplateTypeArgFromVariant(const Type *varType, unsigned i) { 1035f757f3fSDimitry Andric std::optional<ArrayRef<TemplateArgument>> VariantTemplates = 1045f757f3fSDimitry Andric getTemplateArgsFromVariant(varType); 1055f757f3fSDimitry Andric if (!VariantTemplates) 1065f757f3fSDimitry Andric return {}; 1075f757f3fSDimitry Andric 1085f757f3fSDimitry Andric return (*VariantTemplates)[i].getAsType(); 1095f757f3fSDimitry Andric } 1105f757f3fSDimitry Andric 1115f757f3fSDimitry Andric static bool isVowel(char a) { 1125f757f3fSDimitry Andric switch (a) { 1135f757f3fSDimitry Andric case 'a': 1145f757f3fSDimitry Andric case 'e': 1155f757f3fSDimitry Andric case 'i': 1165f757f3fSDimitry Andric case 'o': 1175f757f3fSDimitry Andric case 'u': 1185f757f3fSDimitry Andric return true; 1195f757f3fSDimitry Andric default: 1205f757f3fSDimitry Andric return false; 1215f757f3fSDimitry Andric } 1225f757f3fSDimitry Andric } 1235f757f3fSDimitry Andric 1245f757f3fSDimitry Andric static llvm::StringRef indefiniteArticleBasedOnVowel(char a) { 1255f757f3fSDimitry Andric if (isVowel(a)) 1265f757f3fSDimitry Andric return "an"; 1275f757f3fSDimitry Andric return "a"; 1285f757f3fSDimitry Andric } 1295f757f3fSDimitry Andric 1305f757f3fSDimitry Andric class StdVariantChecker : public Checker<eval::Call, check::RegionChanges> { 1315f757f3fSDimitry Andric // Call descriptors to find relevant calls 132*0fca6ea1SDimitry Andric CallDescription VariantConstructor{CDM::CXXMethod, 133*0fca6ea1SDimitry Andric {"std", "variant", "variant"}}; 134*0fca6ea1SDimitry Andric CallDescription VariantAssignmentOperator{CDM::CXXMethod, 135*0fca6ea1SDimitry Andric {"std", "variant", "operator="}}; 136*0fca6ea1SDimitry Andric CallDescription StdGet{CDM::SimpleFunc, {"std", "get"}, 1, 1}; 1375f757f3fSDimitry Andric 1385f757f3fSDimitry Andric BugType BadVariantType{this, "BadVariantType", "BadVariantType"}; 1395f757f3fSDimitry Andric 1405f757f3fSDimitry Andric public: 1415f757f3fSDimitry Andric ProgramStateRef checkRegionChanges(ProgramStateRef State, 1425f757f3fSDimitry Andric const InvalidatedSymbols *, 1435f757f3fSDimitry Andric ArrayRef<const MemRegion *>, 1445f757f3fSDimitry Andric ArrayRef<const MemRegion *> Regions, 1455f757f3fSDimitry Andric const LocationContext *, 1465f757f3fSDimitry Andric const CallEvent *Call) const { 1475f757f3fSDimitry Andric if (!Call) 1485f757f3fSDimitry Andric return State; 1495f757f3fSDimitry Andric 1505f757f3fSDimitry Andric return removeInformationStoredForDeadInstances<VariantHeldTypeMap>( 1515f757f3fSDimitry Andric *Call, State, Regions); 1525f757f3fSDimitry Andric } 1535f757f3fSDimitry Andric 1545f757f3fSDimitry Andric bool evalCall(const CallEvent &Call, CheckerContext &C) const { 1555f757f3fSDimitry Andric // Check if the call was not made from a system header. If it was then 1565f757f3fSDimitry Andric // we do an early return because it is part of the implementation. 1575f757f3fSDimitry Andric if (Call.isCalledFromSystemHeader()) 1585f757f3fSDimitry Andric return false; 1595f757f3fSDimitry Andric 1605f757f3fSDimitry Andric if (StdGet.matches(Call)) 1615f757f3fSDimitry Andric return handleStdGetCall(Call, C); 1625f757f3fSDimitry Andric 1635f757f3fSDimitry Andric // First check if a constructor call is happening. If it is a 1645f757f3fSDimitry Andric // constructor call, check if it is an std::variant constructor call. 1655f757f3fSDimitry Andric bool IsVariantConstructor = 1665f757f3fSDimitry Andric isa<CXXConstructorCall>(Call) && VariantConstructor.matches(Call); 1675f757f3fSDimitry Andric bool IsVariantAssignmentOperatorCall = 1685f757f3fSDimitry Andric isa<CXXMemberOperatorCall>(Call) && 1695f757f3fSDimitry Andric VariantAssignmentOperator.matches(Call); 1705f757f3fSDimitry Andric 1715f757f3fSDimitry Andric if (IsVariantConstructor || IsVariantAssignmentOperatorCall) { 1725f757f3fSDimitry Andric if (Call.getNumArgs() == 0 && IsVariantConstructor) { 1735f757f3fSDimitry Andric handleDefaultConstructor(cast<CXXConstructorCall>(&Call), C); 1745f757f3fSDimitry Andric return true; 1755f757f3fSDimitry Andric } 1765f757f3fSDimitry Andric 1775f757f3fSDimitry Andric // FIXME Later this checker should be extended to handle constructors 1785f757f3fSDimitry Andric // with multiple arguments. 1795f757f3fSDimitry Andric if (Call.getNumArgs() != 1) 1805f757f3fSDimitry Andric return false; 1815f757f3fSDimitry Andric 1825f757f3fSDimitry Andric SVal ThisSVal; 1835f757f3fSDimitry Andric if (IsVariantConstructor) { 1845f757f3fSDimitry Andric const auto &AsConstructorCall = cast<CXXConstructorCall>(Call); 1855f757f3fSDimitry Andric ThisSVal = AsConstructorCall.getCXXThisVal(); 1865f757f3fSDimitry Andric } else if (IsVariantAssignmentOperatorCall) { 1875f757f3fSDimitry Andric const auto &AsMemberOpCall = cast<CXXMemberOperatorCall>(Call); 1885f757f3fSDimitry Andric ThisSVal = AsMemberOpCall.getCXXThisVal(); 1895f757f3fSDimitry Andric } else { 1905f757f3fSDimitry Andric return false; 1915f757f3fSDimitry Andric } 1925f757f3fSDimitry Andric 1935f757f3fSDimitry Andric handleConstructorAndAssignment<VariantHeldTypeMap>(Call, C, ThisSVal); 1945f757f3fSDimitry Andric return true; 1955f757f3fSDimitry Andric } 1965f757f3fSDimitry Andric return false; 1975f757f3fSDimitry Andric } 1985f757f3fSDimitry Andric 1995f757f3fSDimitry Andric private: 2005f757f3fSDimitry Andric // The default constructed std::variant must be handled separately 2015f757f3fSDimitry Andric // by default the std::variant is going to hold a default constructed instance 2025f757f3fSDimitry Andric // of the first type of the possible types 2035f757f3fSDimitry Andric void handleDefaultConstructor(const CXXConstructorCall *ConstructorCall, 2045f757f3fSDimitry Andric CheckerContext &C) const { 2055f757f3fSDimitry Andric SVal ThisSVal = ConstructorCall->getCXXThisVal(); 2065f757f3fSDimitry Andric 2075f757f3fSDimitry Andric const auto *const ThisMemRegion = ThisSVal.getAsRegion(); 2085f757f3fSDimitry Andric if (!ThisMemRegion) 2095f757f3fSDimitry Andric return; 2105f757f3fSDimitry Andric 2115f757f3fSDimitry Andric std::optional<QualType> DefaultType = getNthTemplateTypeArgFromVariant( 2125f757f3fSDimitry Andric ThisSVal.getType(C.getASTContext())->getPointeeType().getTypePtr(), 0); 2135f757f3fSDimitry Andric if (!DefaultType) 2145f757f3fSDimitry Andric return; 2155f757f3fSDimitry Andric 2165f757f3fSDimitry Andric ProgramStateRef State = ConstructorCall->getState(); 2175f757f3fSDimitry Andric State = State->set<VariantHeldTypeMap>(ThisMemRegion, *DefaultType); 2185f757f3fSDimitry Andric C.addTransition(State); 2195f757f3fSDimitry Andric } 2205f757f3fSDimitry Andric 2215f757f3fSDimitry Andric bool handleStdGetCall(const CallEvent &Call, CheckerContext &C) const { 2225f757f3fSDimitry Andric ProgramStateRef State = Call.getState(); 2235f757f3fSDimitry Andric 2245f757f3fSDimitry Andric const auto &ArgType = Call.getArgSVal(0) 2255f757f3fSDimitry Andric .getType(C.getASTContext()) 2265f757f3fSDimitry Andric ->getPointeeType() 2275f757f3fSDimitry Andric .getTypePtr(); 2285f757f3fSDimitry Andric // We have to make sure that the argument is an std::variant. 2295f757f3fSDimitry Andric // There is another std::get with std::pair argument 2305f757f3fSDimitry Andric if (!isStdVariant(ArgType)) 2315f757f3fSDimitry Andric return false; 2325f757f3fSDimitry Andric 2335f757f3fSDimitry Andric // Get the mem region of the argument std::variant and look up the type 2345f757f3fSDimitry Andric // information that we know about it. 2355f757f3fSDimitry Andric const MemRegion *ArgMemRegion = Call.getArgSVal(0).getAsRegion(); 2365f757f3fSDimitry Andric const QualType *StoredType = State->get<VariantHeldTypeMap>(ArgMemRegion); 2375f757f3fSDimitry Andric if (!StoredType) 2385f757f3fSDimitry Andric return false; 2395f757f3fSDimitry Andric 2405f757f3fSDimitry Andric const CallExpr *CE = cast<CallExpr>(Call.getOriginExpr()); 2415f757f3fSDimitry Andric const FunctionDecl *FD = CE->getDirectCallee(); 2425f757f3fSDimitry Andric if (FD->getTemplateSpecializationArgs()->size() < 1) 2435f757f3fSDimitry Andric return false; 2445f757f3fSDimitry Andric 2455f757f3fSDimitry Andric const auto &TypeOut = FD->getTemplateSpecializationArgs()->asArray()[0]; 2465f757f3fSDimitry Andric // std::get's first template parameter can be the type we want to get 2475f757f3fSDimitry Andric // out of the std::variant or a natural number which is the position of 2485f757f3fSDimitry Andric // the requested type in the argument type list of the std::variant's 2495f757f3fSDimitry Andric // argument. 2505f757f3fSDimitry Andric QualType RetrievedType; 2515f757f3fSDimitry Andric switch (TypeOut.getKind()) { 2525f757f3fSDimitry Andric case TemplateArgument::ArgKind::Type: 2535f757f3fSDimitry Andric RetrievedType = TypeOut.getAsType(); 2545f757f3fSDimitry Andric break; 2555f757f3fSDimitry Andric case TemplateArgument::ArgKind::Integral: 2565f757f3fSDimitry Andric // In the natural number case we look up which type corresponds to the 2575f757f3fSDimitry Andric // number. 2585f757f3fSDimitry Andric if (std::optional<QualType> NthTemplate = 2595f757f3fSDimitry Andric getNthTemplateTypeArgFromVariant( 2605f757f3fSDimitry Andric ArgType, TypeOut.getAsIntegral().getSExtValue())) { 2615f757f3fSDimitry Andric RetrievedType = *NthTemplate; 2625f757f3fSDimitry Andric break; 2635f757f3fSDimitry Andric } 2645f757f3fSDimitry Andric [[fallthrough]]; 2655f757f3fSDimitry Andric default: 2665f757f3fSDimitry Andric return false; 2675f757f3fSDimitry Andric } 2685f757f3fSDimitry Andric 2695f757f3fSDimitry Andric QualType RetrievedCanonicalType = RetrievedType.getCanonicalType(); 2705f757f3fSDimitry Andric QualType StoredCanonicalType = StoredType->getCanonicalType(); 2715f757f3fSDimitry Andric if (RetrievedCanonicalType == StoredCanonicalType) 2725f757f3fSDimitry Andric return true; 2735f757f3fSDimitry Andric 2745f757f3fSDimitry Andric ExplodedNode *ErrNode = C.generateNonFatalErrorNode(); 2755f757f3fSDimitry Andric if (!ErrNode) 2765f757f3fSDimitry Andric return false; 2775f757f3fSDimitry Andric llvm::SmallString<128> Str; 2785f757f3fSDimitry Andric llvm::raw_svector_ostream OS(Str); 2795f757f3fSDimitry Andric std::string StoredTypeName = StoredType->getAsString(); 2805f757f3fSDimitry Andric std::string RetrievedTypeName = RetrievedType.getAsString(); 2815f757f3fSDimitry Andric OS << "std::variant " << ArgMemRegion->getDescriptiveName() << " held " 2825f757f3fSDimitry Andric << indefiniteArticleBasedOnVowel(StoredTypeName[0]) << " \'" 2835f757f3fSDimitry Andric << StoredTypeName << "\', not " 2845f757f3fSDimitry Andric << indefiniteArticleBasedOnVowel(RetrievedTypeName[0]) << " \'" 2855f757f3fSDimitry Andric << RetrievedTypeName << "\'"; 2865f757f3fSDimitry Andric auto R = std::make_unique<PathSensitiveBugReport>(BadVariantType, OS.str(), 2875f757f3fSDimitry Andric ErrNode); 2885f757f3fSDimitry Andric C.emitReport(std::move(R)); 2895f757f3fSDimitry Andric return true; 2905f757f3fSDimitry Andric } 2915f757f3fSDimitry Andric }; 2925f757f3fSDimitry Andric 2935f757f3fSDimitry Andric bool clang::ento::shouldRegisterStdVariantChecker( 2945f757f3fSDimitry Andric clang::ento::CheckerManager const &mgr) { 2955f757f3fSDimitry Andric return true; 2965f757f3fSDimitry Andric } 2975f757f3fSDimitry Andric 2985f757f3fSDimitry Andric void clang::ento::registerStdVariantChecker(clang::ento::CheckerManager &mgr) { 2995f757f3fSDimitry Andric mgr.registerChecker<StdVariantChecker>(); 3005f757f3fSDimitry Andric } 301