aboutsummaryrefslogtreecommitdiff
path: root/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp')
-rw-r--r--contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp300
1 files changed, 300 insertions, 0 deletions
diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp
new file mode 100644
index 000000000000..19877964bd90
--- /dev/null
+++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp
@@ -0,0 +1,300 @@
+//===- StdVariantChecker.cpp -------------------------------------*- C++ -*-==//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/AST/Type.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/CheckerManager.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"
+#include "llvm/ADT/FoldingSet.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Casting.h"
+#include <optional>
+#include <string_view>
+
+#include "TaggedUnionModeling.h"
+
+using namespace clang;
+using namespace ento;
+using namespace tagged_union_modeling;
+
+REGISTER_MAP_WITH_PROGRAMSTATE(VariantHeldTypeMap, const MemRegion *, QualType)
+
+namespace clang::ento::tagged_union_modeling {
+
+const CXXConstructorDecl *
+getConstructorDeclarationForCall(const CallEvent &Call) {
+ const auto *ConstructorCall = dyn_cast<CXXConstructorCall>(&Call);
+ if (!ConstructorCall)
+ return nullptr;
+
+ return ConstructorCall->getDecl();
+}
+
+bool isCopyConstructorCall(const CallEvent &Call) {
+ if (const CXXConstructorDecl *ConstructorDecl =
+ getConstructorDeclarationForCall(Call))
+ return ConstructorDecl->isCopyConstructor();
+ return false;
+}
+
+bool isCopyAssignmentCall(const CallEvent &Call) {
+ const Decl *CopyAssignmentDecl = Call.getDecl();
+
+ if (const auto *AsMethodDecl =
+ dyn_cast_or_null<CXXMethodDecl>(CopyAssignmentDecl))
+ return AsMethodDecl->isCopyAssignmentOperator();
+ return false;
+}
+
+bool isMoveConstructorCall(const CallEvent &Call) {
+ const CXXConstructorDecl *ConstructorDecl =
+ getConstructorDeclarationForCall(Call);
+ if (!ConstructorDecl)
+ return false;
+
+ return ConstructorDecl->isMoveConstructor();
+}
+
+bool isMoveAssignmentCall(const CallEvent &Call) {
+ const Decl *CopyAssignmentDecl = Call.getDecl();
+
+ const auto *AsMethodDecl =
+ dyn_cast_or_null<CXXMethodDecl>(CopyAssignmentDecl);
+ if (!AsMethodDecl)
+ return false;
+
+ return AsMethodDecl->isMoveAssignmentOperator();
+}
+
+bool isStdType(const Type *Type, llvm::StringRef TypeName) {
+ auto *Decl = Type->getAsRecordDecl();
+ if (!Decl)
+ return false;
+ return (Decl->getName() == TypeName) && Decl->isInStdNamespace();
+}
+
+bool isStdVariant(const Type *Type) {
+ return isStdType(Type, llvm::StringLiteral("variant"));
+}
+
+} // end of namespace clang::ento::tagged_union_modeling
+
+static std::optional<ArrayRef<TemplateArgument>>
+getTemplateArgsFromVariant(const Type *VariantType) {
+ const auto *TempSpecType = VariantType->getAs<TemplateSpecializationType>();
+ if (!TempSpecType)
+ return {};
+
+ return TempSpecType->template_arguments();
+}
+
+static std::optional<QualType>
+getNthTemplateTypeArgFromVariant(const Type *varType, unsigned i) {
+ std::optional<ArrayRef<TemplateArgument>> VariantTemplates =
+ getTemplateArgsFromVariant(varType);
+ if (!VariantTemplates)
+ return {};
+
+ return (*VariantTemplates)[i].getAsType();
+}
+
+static bool isVowel(char a) {
+ switch (a) {
+ case 'a':
+ case 'e':
+ case 'i':
+ case 'o':
+ case 'u':
+ return true;
+ default:
+ return false;
+ }
+}
+
+static llvm::StringRef indefiniteArticleBasedOnVowel(char a) {
+ if (isVowel(a))
+ return "an";
+ return "a";
+}
+
+class StdVariantChecker : public Checker<eval::Call, check::RegionChanges> {
+ // Call descriptors to find relevant calls
+ CallDescription VariantConstructor{CDM::CXXMethod,
+ {"std", "variant", "variant"}};
+ CallDescription VariantAssignmentOperator{CDM::CXXMethod,
+ {"std", "variant", "operator="}};
+ CallDescription StdGet{CDM::SimpleFunc, {"std", "get"}, 1, 1};
+
+ BugType BadVariantType{this, "BadVariantType", "BadVariantType"};
+
+public:
+ ProgramStateRef checkRegionChanges(ProgramStateRef State,
+ const InvalidatedSymbols *,
+ ArrayRef<const MemRegion *>,
+ ArrayRef<const MemRegion *> Regions,
+ const LocationContext *,
+ const CallEvent *Call) const {
+ if (!Call)
+ return State;
+
+ return removeInformationStoredForDeadInstances<VariantHeldTypeMap>(
+ *Call, State, Regions);
+ }
+
+ bool evalCall(const CallEvent &Call, CheckerContext &C) const {
+ // Check if the call was not made from a system header. If it was then
+ // we do an early return because it is part of the implementation.
+ if (Call.isCalledFromSystemHeader())
+ return false;
+
+ if (StdGet.matches(Call))
+ return handleStdGetCall(Call, C);
+
+ // First check if a constructor call is happening. If it is a
+ // constructor call, check if it is an std::variant constructor call.
+ bool IsVariantConstructor =
+ isa<CXXConstructorCall>(Call) && VariantConstructor.matches(Call);
+ bool IsVariantAssignmentOperatorCall =
+ isa<CXXMemberOperatorCall>(Call) &&
+ VariantAssignmentOperator.matches(Call);
+
+ if (IsVariantConstructor || IsVariantAssignmentOperatorCall) {
+ if (Call.getNumArgs() == 0 && IsVariantConstructor) {
+ handleDefaultConstructor(cast<CXXConstructorCall>(&Call), C);
+ return true;
+ }
+
+ // FIXME Later this checker should be extended to handle constructors
+ // with multiple arguments.
+ if (Call.getNumArgs() != 1)
+ return false;
+
+ SVal ThisSVal;
+ if (IsVariantConstructor) {
+ const auto &AsConstructorCall = cast<CXXConstructorCall>(Call);
+ ThisSVal = AsConstructorCall.getCXXThisVal();
+ } else if (IsVariantAssignmentOperatorCall) {
+ const auto &AsMemberOpCall = cast<CXXMemberOperatorCall>(Call);
+ ThisSVal = AsMemberOpCall.getCXXThisVal();
+ } else {
+ return false;
+ }
+
+ handleConstructorAndAssignment<VariantHeldTypeMap>(Call, C, ThisSVal);
+ return true;
+ }
+ return false;
+ }
+
+private:
+ // The default constructed std::variant must be handled separately
+ // by default the std::variant is going to hold a default constructed instance
+ // of the first type of the possible types
+ void handleDefaultConstructor(const CXXConstructorCall *ConstructorCall,
+ CheckerContext &C) const {
+ SVal ThisSVal = ConstructorCall->getCXXThisVal();
+
+ const auto *const ThisMemRegion = ThisSVal.getAsRegion();
+ if (!ThisMemRegion)
+ return;
+
+ std::optional<QualType> DefaultType = getNthTemplateTypeArgFromVariant(
+ ThisSVal.getType(C.getASTContext())->getPointeeType().getTypePtr(), 0);
+ if (!DefaultType)
+ return;
+
+ ProgramStateRef State = ConstructorCall->getState();
+ State = State->set<VariantHeldTypeMap>(ThisMemRegion, *DefaultType);
+ C.addTransition(State);
+ }
+
+ bool handleStdGetCall(const CallEvent &Call, CheckerContext &C) const {
+ ProgramStateRef State = Call.getState();
+
+ const auto &ArgType = Call.getArgSVal(0)
+ .getType(C.getASTContext())
+ ->getPointeeType()
+ .getTypePtr();
+ // We have to make sure that the argument is an std::variant.
+ // There is another std::get with std::pair argument
+ if (!isStdVariant(ArgType))
+ return false;
+
+ // Get the mem region of the argument std::variant and look up the type
+ // information that we know about it.
+ const MemRegion *ArgMemRegion = Call.getArgSVal(0).getAsRegion();
+ const QualType *StoredType = State->get<VariantHeldTypeMap>(ArgMemRegion);
+ if (!StoredType)
+ return false;
+
+ const CallExpr *CE = cast<CallExpr>(Call.getOriginExpr());
+ const FunctionDecl *FD = CE->getDirectCallee();
+ if (FD->getTemplateSpecializationArgs()->size() < 1)
+ return false;
+
+ const auto &TypeOut = FD->getTemplateSpecializationArgs()->asArray()[0];
+ // std::get's first template parameter can be the type we want to get
+ // out of the std::variant or a natural number which is the position of
+ // the requested type in the argument type list of the std::variant's
+ // argument.
+ QualType RetrievedType;
+ switch (TypeOut.getKind()) {
+ case TemplateArgument::ArgKind::Type:
+ RetrievedType = TypeOut.getAsType();
+ break;
+ case TemplateArgument::ArgKind::Integral:
+ // In the natural number case we look up which type corresponds to the
+ // number.
+ if (std::optional<QualType> NthTemplate =
+ getNthTemplateTypeArgFromVariant(
+ ArgType, TypeOut.getAsIntegral().getSExtValue())) {
+ RetrievedType = *NthTemplate;
+ break;
+ }
+ [[fallthrough]];
+ default:
+ return false;
+ }
+
+ QualType RetrievedCanonicalType = RetrievedType.getCanonicalType();
+ QualType StoredCanonicalType = StoredType->getCanonicalType();
+ if (RetrievedCanonicalType == StoredCanonicalType)
+ return true;
+
+ ExplodedNode *ErrNode = C.generateNonFatalErrorNode();
+ if (!ErrNode)
+ return false;
+ llvm::SmallString<128> Str;
+ llvm::raw_svector_ostream OS(Str);
+ std::string StoredTypeName = StoredType->getAsString();
+ std::string RetrievedTypeName = RetrievedType.getAsString();
+ OS << "std::variant " << ArgMemRegion->getDescriptiveName() << " held "
+ << indefiniteArticleBasedOnVowel(StoredTypeName[0]) << " \'"
+ << StoredTypeName << "\', not "
+ << indefiniteArticleBasedOnVowel(RetrievedTypeName[0]) << " \'"
+ << RetrievedTypeName << "\'";
+ auto R = std::make_unique<PathSensitiveBugReport>(BadVariantType, OS.str(),
+ ErrNode);
+ C.emitReport(std::move(R));
+ return true;
+ }
+};
+
+bool clang::ento::shouldRegisterStdVariantChecker(
+ clang::ento::CheckerManager const &mgr) {
+ return true;
+}
+
+void clang::ento::registerStdVariantChecker(clang::ento::CheckerManager &mgr) {
+ mgr.registerChecker<StdVariantChecker>();
+}