[clang] [analyzer] Add std::variant checker (PR #66481)

via cfe-commits cfe-commits at lists.llvm.org
Fri Sep 15 07:00:42 PDT 2023


================
@@ -0,0 +1,312 @@
+//===- 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 "llvm/ADT/FoldingSet.h"
+
+#include "TaggedUnionModeling.h"
+
+using namespace clang;
+using namespace ento;
+using namespace variant_modeling;
+
+REGISTER_MAP_WITH_PROGRAMSTATE(VariantHeldTypeMap, const MemRegion *, QualType)
+
+namespace clang {
+namespace ento {
+namespace variant_modeling {
+
+// Returns the CallEvent representing the caller of the function
+// It is needed because the CallEvent class does not contain enough information
+// to tell who called it. Checker context is needed.
+CallEventRef<> getCaller(const CallEvent &Call, const ProgramStateRef &State) {
+  const auto *CallLocationContext = Call.getLocationContext();
+  if (!CallLocationContext) {
+    return nullptr;
+  }
+
+  if (CallLocationContext->inTopFrame()) {
+    return nullptr;
+  }
+  const auto *CallStackFrameContext = CallLocationContext->getStackFrame();
+  if (!CallStackFrameContext) {
+    return nullptr;
+  }
+
+  CallEventManager &CEMgr = State->getStateManager().getCallEventManager();
+  return CEMgr.getCaller(CallStackFrameContext, State);
+}
+
+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) {
+  const CXXConstructorDecl *ConstructorDecl =
+      getConstructorDeclarationForCall(Call);
+  if (!ConstructorDecl) {
+    return false;
+  }
+  return ConstructorDecl->isCopyConstructor();
+}
+
+bool isCopyAssignmentCall(const CallEvent &Call) {
+  const Decl *CopyAssignmentDecl = Call.getDecl();
+  if (!CopyAssignmentDecl) {
+    return false;
+  }
+  const auto *AsMethodDecl = dyn_cast<CXXMethodDecl>(CopyAssignmentDecl);
+  if (!AsMethodDecl) {
+    return false;
+  }
+  return AsMethodDecl->isCopyAssignmentOperator();
+}
+
+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();
+  if (!CopyAssignmentDecl) {
+    return false;
+  }
+  const auto *AsMethodDecl = dyn_cast<CXXMethodDecl>(CopyAssignmentDecl);
+  if (!AsMethodDecl) {
+    return false;
+  }
+  return AsMethodDecl->isMoveAssignmentOperator();
+}
+
+const TemplateArgument &getFirstTemplateArgument(const CallEvent &Call) {
+  const CallExpr *CE = cast<CallExpr>(Call.getOriginExpr());
+  const FunctionDecl *FD = CE->getDirectCallee();
+  assert(1 <= FD->getTemplateSpecializationArgs()->asArray().size() &&
+         "std::get should have at least 1 template argument!");
+  return FD->getTemplateSpecializationArgs()->asArray()[0];
+}
+
+bool isStdType(const Type *Type, const std::string &TypeName) {
+  auto *Decl = Type->getAsRecordDecl();
+  if (!Decl) {
+    return false;
+  }
+
+  return (Decl->getNameAsString() == TypeName) && Decl->isInStdNamespace();
+}
+
+bool isStdVariant(const Type *Type) {
+  return isStdType(Type, std::string("variant"));
+}
+
+bool calledFromSystemHeader(const CallEvent &Call,
+                            const ProgramStateRef &State) {
+  auto Caller = getCaller(Call, State);
+  if (Caller) {
+    return Caller->isInSystemHeader();
+  }
+  return false;
+}
+
+bool calledFromSystemHeader(const CallEvent &Call, CheckerContext &C) {
+  return calledFromSystemHeader(Call, C.getState());
+}
+
+} // end of namespace variant_modeling
+} // end of namespace ento
+} // end of namespace clang
+
+static ArrayRef<TemplateArgument>
+getTemplateArgsFromVariant(const Type *VariantType) {
+  const auto *TempSpecType = VariantType->getAs<TemplateSpecializationType>();
+  assert(TempSpecType &&
+         "We are in a variant instance. It must be a template specialization!");
+  return TempSpecType->template_arguments();
+}
+
+static QualType getNthTemplateTypeArgFromVariant(const Type *varType,
+                                                 unsigned i) {
+  return getTemplateArgsFromVariant(varType)[i].getAsType();
+}
+
+class StdVariantChecker : public Checker<eval::Call, check::RegionChanges> {
+  // Call descriptors to find relevant calls
+  CallDescription VariantConstructor{{"std", "variant", "variant"}};
+  CallDescription VariantAsOp{{"std", "variant", "operator="}};
+  CallDescription StdGet{{"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 {
+    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 (calledFromSystemHeader(Call, C)) {
+      return false;
+    }
+
+    if (StdGet.matches(Call)) {
+      return handleStdGetCall(Call, C);
+    }
+
+    bool IsVariantConstructor =
+        isa<CXXConstructorCall>(Call) && VariantConstructor.matches(Call);
+    bool IsVariantAssignmentOperatorCall =
+        isa<CXXMemberOperatorCall>(Call) && VariantAsOp.matches(Call);
+
+    if (IsVariantConstructor || IsVariantAssignmentOperatorCall) {
+      if (IsVariantConstructor && Call.getNumArgs() == 0) {
+        handleDefaultConstructor(Call, C);
+        return true;
+      }
+      if (Call.getNumArgs() != 1) {
+        return true;
----------------
DonatNagyE wrote:

If I understand correctly, this marks multi-argument variant constructors as "evaluated" without doing anything. Please add a comment that mentions this and explains why is this the "right thing to do".

https://github.com/llvm/llvm-project/pull/66481


More information about the cfe-commits mailing list