xref: /freebsd/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp (revision 0fca6ea1d4eea4c934cfff25ac9ee8ad6fe95583)
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