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 *
getConstructorDeclarationForCall(const CallEvent & Call)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
isCopyConstructorCall(const CallEvent & Call)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
isCopyAssignmentCall(const CallEvent & Call)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
isMoveConstructorCall(const CallEvent & Call)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
isMoveAssignmentCall(const CallEvent & Call)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
isStdType(const Type * Type,llvm::StringRef TypeName)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
isStdVariant(const Type * Type)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>>
getTemplateArgsFromVariant(const Type * VariantType)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>
getNthTemplateTypeArgFromVariant(const Type * varType,unsigned i)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
isVowel(char a)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
indefiniteArticleBasedOnVowel(char a)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:
checkRegionChanges(ProgramStateRef State,const InvalidatedSymbols *,ArrayRef<const MemRegion * >,ArrayRef<const MemRegion * > Regions,const LocationContext *,const CallEvent * Call) const1415f757f3fSDimitry 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
evalCall(const CallEvent & Call,CheckerContext & C) const1545f757f3fSDimitry 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
handleDefaultConstructor(const CXXConstructorCall * ConstructorCall,CheckerContext & C) const2035f757f3fSDimitry 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
handleStdGetCall(const CallEvent & Call,CheckerContext & C) const2215f757f3fSDimitry 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
shouldRegisterStdVariantChecker(clang::ento::CheckerManager const & mgr)2935f757f3fSDimitry Andric bool clang::ento::shouldRegisterStdVariantChecker(
2945f757f3fSDimitry Andric clang::ento::CheckerManager const &mgr) {
2955f757f3fSDimitry Andric return true;
2965f757f3fSDimitry Andric }
2975f757f3fSDimitry Andric
registerStdVariantChecker(clang::ento::CheckerManager & mgr)2985f757f3fSDimitry Andric void clang::ento::registerStdVariantChecker(clang::ento::CheckerManager &mgr) {
2995f757f3fSDimitry Andric mgr.registerChecker<StdVariantChecker>();
3005f757f3fSDimitry Andric }
301