[clang] [analyzer] Add std::any checker (PR #76580)

via cfe-commits cfe-commits at lists.llvm.org
Sat Dec 30 03:13:57 PST 2023


================
@@ -0,0 +1,201 @@
+//===- StdAnyChecker.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/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/Support/ErrorHandling.h"
+
+#include "TaggedUnionModeling.h"
+
+using namespace clang;
+using namespace ento;
+using namespace tagged_union_modeling;
+
+REGISTER_MAP_WITH_PROGRAMSTATE(AnyHeldTypeMap, const MemRegion *, QualType)
+
+class StdAnyChecker : public Checker<eval::Call, check::RegionChanges> {
+  CallDescription AnyConstructor{{"std", "any", "any"}};
+  CallDescription AnyAsOp{{"std", "any", "operator="}};
+  CallDescription AnyReset{{"std", "any", "reset"}, 0, 0};
+  CallDescription AnyCast{{"std", "any_cast"}, 1, 1};
+
+  BugType BadAnyType{this, "BadAnyType", "BadAnyType"};
+  BugType NullAnyType{this, "NullAnyType", "NullAnyType"};
+
+public:
+  ProgramStateRef
+  checkRegionChanges(ProgramStateRef State,
+                     const InvalidatedSymbols *Invalidated,
+                     ArrayRef<const MemRegion *> ExplicitRegions,
+                     ArrayRef<const MemRegion *> Regions,
+                     const LocationContext *LCtx, const CallEvent *Call) const {
+    if (!Call)
+      return State;
+
+    return removeInformationStoredForDeadInstances<AnyHeldTypeMap>(*Call, State,
+                                                                   Regions);
+  }
+
+  bool evalCall(const CallEvent &Call, CheckerContext &C) const {
+    // Do not take implementation details into consideration
+    if (Call.isCalledFromSystemHeader())
+      return false;
+
+    if (AnyCast.matches(Call))
+      return handleAnyCastCall(Call, C);
+
+    if (AnyReset.matches(Call)) {
+      const auto *AsMemberCall = dyn_cast<CXXMemberCall>(&Call);
+      if (!AsMemberCall)
+        return false;
+
+      const auto *ThisMemRegion = AsMemberCall->getCXXThisVal().getAsRegion();
+      if (!ThisMemRegion)
+        return false;
+
+      setNullTypeAny(ThisMemRegion, C);
+      return true;
+    }
+
+    bool IsAnyConstructor =
+        isa<CXXConstructorCall>(Call) && AnyConstructor.matches(Call);
+    bool IsAnyAssignmentOperatorCall =
+        isa<CXXMemberOperatorCall>(Call) && AnyAsOp.matches(Call);
+
+    if (IsAnyConstructor || IsAnyAssignmentOperatorCall) {
+      auto State = C.getState();
+      SVal ThisSVal = [&]() {
+        if (IsAnyConstructor) {
+          const auto *AsConstructorCall = dyn_cast<CXXConstructorCall>(&Call);
+          return AsConstructorCall->getCXXThisVal();
+        }
+        if (IsAnyAssignmentOperatorCall) {
+          const auto *AsMemberOpCall = dyn_cast<CXXMemberOperatorCall>(&Call);
+          return AsMemberOpCall->getCXXThisVal();
+        }
+        llvm_unreachable("We must have an assignment operator or constructor");
+      }();
+
+      // default constructor call
+      // in this case the any holds a null type
+      if (Call.getNumArgs() == 0) {
+        const auto *ThisMemRegion = ThisSVal.getAsRegion();
+        setNullTypeAny(ThisMemRegion, C);
+        return true;
+      }
+
+      if (Call.getNumArgs() != 1)
+        return false;
+
+      handleConstructorAndAssignment<AnyHeldTypeMap>(Call, C, ThisSVal);
+      return true;
+    }
+    return false;
+  }
+
+private:
+  // When an std::any is rested or default constructed it has a null type.
+  // We represent it by storing a null QualType.
+  void setNullTypeAny(const MemRegion *Mem, CheckerContext &C) const {
+    auto State = C.getState();
+    State = State->set<AnyHeldTypeMap>(Mem, QualType{});
+    C.addTransition(State);
+  }
+
+  // this function name is terrible
+  bool handleAnyCastCall(const CallEvent &Call, CheckerContext &C) const {
+    auto State = C.getState();
+
+    if (Call.getNumArgs() != 1)
+      return false;
+
+    auto ArgSVal = Call.getArgSVal(0);
+
+    // The argument is aether a const reference or a right value reference
+    //  We need the type referred
+    const auto *ArgType = ArgSVal.getType(C.getASTContext())
+                              .getTypePtr()
+                              ->getPointeeType()
+                              .getTypePtr();
----------------
whisperity wrote:

I guess `getTypePtr()` alone should be safe, but there are *at least* two potentially unsafe dereferences here, the `getTypePtr()->` and the `getPointeeType().` Looking scary... 

Are we sure we will always get constructs that are dereferenceable this many times, in this particular order?
Wouldn't it be worth at least adding some intermediate asserts, just to make sure if this thing blows up somewhere, we can get to know what the error was?

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


More information about the cfe-commits mailing list