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

Gábor Spaits via cfe-commits cfe-commits at lists.llvm.org
Fri Dec 29 11:03:44 PST 2023


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

Add a checker to detect bad `std::any` type accesses.
It warns, when the active type is different from the requested type when calling `std::any_cast`:
```cpp
void anyCast() {
  std::any a = 5;
  char c = std::any_cast<char>(a); // // warn: "int" is the active alternative
 }
```
It also warns, when the `std::any` instance is empty:
```cpp
 void noTypeHeld() {
  std::any a;
  int i = std::any_cast<int>(a); // warn: any 'a' is empty
 }
```
This checker works in a similar way to `std::variant` checker: Recognizes when the active type of an `std::any` instance changes and stores this information. Later on, when the data hold in this instance is accessed with `std::any_cast` it is checked if the active type is retrieved or not. If not then a warning is emitted.

This checker does not produce false positives. It is conservative: if there is no information of the `std::any` instance or the already recorded information becomes invalid no report will be emitted. 

>From 98b52eb390438402562de96a74771b0132fc71cb Mon Sep 17 00:00:00 2001
From: Gabor Spaits <gaborspaits1 at gmail.com>
Date: Fri, 29 Dec 2023 17:54:34 +0100
Subject: [PATCH] [analyzer] Add std::any checker

---
 clang/docs/analyzer/checkers.rst              |  21 ++
 .../clang/StaticAnalyzer/Checkers/Checkers.td |   4 +
 .../StaticAnalyzer/Checkers/CMakeLists.txt    |   1 +
 .../StaticAnalyzer/Checkers/StdAnyChecker.cpp | 201 ++++++++++++++++++
 .../Checkers/StdVariantChecker.cpp            |  46 ++--
 .../Checkers/TaggedUnionModeling.h            |   9 +-
 .../Inputs/system-header-simulator-cxx.h      |  59 +++++
 clang/test/Analysis/std-any-checker.cpp       | 170 +++++++++++++++
 clang/test/Analysis/std-variant-checker.cpp   |   8 +
 9 files changed, 490 insertions(+), 29 deletions(-)
 create mode 100644 clang/lib/StaticAnalyzer/Checkers/StdAnyChecker.cpp
 create mode 100644 clang/test/Analysis/std-any-checker.cpp

diff --git a/clang/docs/analyzer/checkers.rst b/clang/docs/analyzer/checkers.rst
index bb637cf1b8007b..867bdfc9012253 100644
--- a/clang/docs/analyzer/checkers.rst
+++ b/clang/docs/analyzer/checkers.rst
@@ -2095,6 +2095,27 @@ This checker is a part of ``core.StackAddressEscape``, but is temporarily disabl
                  //       returned block
  }
 
+.. _alpha-core-StdAny:
+
+alpha.core.StdAny (C++)
+"""""""""""""""""""""""
+Check if a value of active type is retrieved from an ``std::any`` instance with ``std::any_cats``
+or if the ``std::any`` instance holds type. In case of bad any type access
+(the accessed type differs from the active type, or the instance has no stored value)
+a warning is emitted. Currently, this checker does not take exception handling into account.
+
+.. code-block:: cpp
+
+ void anyCast() {
+  std::any a = 5;
+  char c = std::any_cast<char>(a); // // warn: "int" is the active alternative
+ }
+
+ void noTypeHeld() {
+  std::any a;
+  int i = std::any_cast<int>(a); // warn: any 'a' is empty
+ }
+
 .. _alpha-core-StdVariant:
 
 alpha.core.StdVariant (C++)
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
index e7774e5a9392d2..954584fadb225d 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -321,6 +321,10 @@ def C11LockChecker : Checker<"C11Lock">,
   Dependencies<[PthreadLockBase]>,
   Documentation<HasDocumentation>;
 
+def StdAnyChecker : Checker<"StdAny">,
+  HelpText<"Check for bad type access for std::any.">,
+  Documentation<HasDocumentation>;
+
 def StdVariantChecker : Checker<"StdVariant">,
   HelpText<"Check for bad type access for std::variant.">,
   Documentation<HasDocumentation>;
diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
index 4443ffd0929388..4e3475ff7f37da 100644
--- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -107,6 +107,7 @@ add_clang_library(clangStaticAnalyzerCheckers
   SmartPtrChecker.cpp
   SmartPtrModeling.cpp
   StackAddrEscapeChecker.cpp
+  StdAnyChecker.cpp
   StdLibraryFunctionsChecker.cpp
   StdVariantChecker.cpp
   STLAlgorithmModeling.cpp
diff --git a/clang/lib/StaticAnalyzer/Checkers/StdAnyChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StdAnyChecker.cpp
new file mode 100644
index 00000000000000..647ee72e555140
--- /dev/null
+++ b/clang/lib/StaticAnalyzer/Checkers/StdAnyChecker.cpp
@@ -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();
+    if (!isStdAny(ArgType))
+      return false;
+
+    const auto *AnyMemRegion = ArgSVal.getAsRegion();
+
+    if (!State->contains<AnyHeldTypeMap>(AnyMemRegion))
+      return false;
+
+    // get the type we are trying to get from any
+    const CallExpr *CE = cast<CallExpr>(Call.getOriginExpr());
+    const FunctionDecl *FD = CE->getDirectCallee();
+    if (FD->getTemplateSpecializationArgs()->size() < 1)
+      return false;
+
+    const auto &FirstTemplateArgument =
+        FD->getTemplateSpecializationArgs()->asArray()[0];
+    if (FirstTemplateArgument.getKind() != TemplateArgument::ArgKind::Type)
+      return false;
+
+    auto TypeOut = FirstTemplateArgument.getAsType();
+    const auto *TypeStored = State->get<AnyHeldTypeMap>(AnyMemRegion);
+
+    // Report when we try to use std::any_cast on an std::any that held a null
+    // type
+    if (TypeStored->isNull()) {
+      ExplodedNode *ErrNode = C.generateNonFatalErrorNode();
+      if (!ErrNode)
+        return false;
+      llvm::SmallString<128> Str;
+      llvm::raw_svector_ostream OS(Str);
+      OS << "std::any " << AnyMemRegion->getDescriptiveName() << " is empty.";
+      auto R = std::make_unique<PathSensitiveBugReport>(NullAnyType, OS.str(),
+                                                        ErrNode);
+      C.emitReport(std::move(R));
+      return true;
+    }
+
+    // Check if the right type is being accessed
+    // There is spacial case for object types.
+    QualType RetrievedCanonicalType = TypeOut.getCanonicalType();
+    QualType StoredCanonicalType = TypeStored->getCanonicalType();
+    if (RetrievedCanonicalType == StoredCanonicalType)
+      return true;
+
+    // Report when the type we want to get out of std::any is wrong
+    ExplodedNode *ErrNode = C.generateNonFatalErrorNode();
+    if (!ErrNode)
+      return false;
+    llvm::SmallString<128> Str;
+    llvm::raw_svector_ostream OS(Str);
+    std::string StoredTypeName = StoredCanonicalType.getAsString();
+    std::string RetrievedTypeName = RetrievedCanonicalType.getAsString();
+    OS << "std::any " << AnyMemRegion->getDescriptiveName() << " held "
+       << indefiniteArticleBasedOnVowel(StoredTypeName[0]) << " \'"
+       << StoredTypeName << "\', not "
+       << indefiniteArticleBasedOnVowel(RetrievedTypeName[0]) << " \'"
+       << RetrievedTypeName << "\'";
+    auto R =
+        std::make_unique<PathSensitiveBugReport>(BadAnyType, OS.str(), ErrNode);
+    C.emitReport(std::move(R));
+    return true;
+  }
+};
+
+bool clang::ento::shouldRegisterStdAnyChecker(
+    clang::ento::CheckerManager const &mgr) {
+  return true;
+}
+
+void clang::ento::registerStdAnyChecker(clang::ento::CheckerManager &mgr) {
+  mgr.registerChecker<StdAnyChecker>();
+}
\ No newline at end of file
diff --git a/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp
index f7b7befe28ee7d..62ca9f0ae836e0 100644
--- a/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp
@@ -17,9 +17,7 @@
 #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"
 
@@ -87,6 +85,28 @@ bool isStdVariant(const Type *Type) {
   return isStdType(Type, llvm::StringLiteral("variant"));
 }
 
+bool isStdAny(const Type *Type) {
+  return isStdType(Type, llvm::StringLiteral("any"));
+}
+
+bool isVowel(char a) {
+  switch (a) {
+  case 'a':
+  case 'e':
+  case 'i':
+  case 'o':
+  case 'u':
+    return true;
+  default:
+    return false;
+  }
+}
+
+llvm::StringRef indefiniteArticleBasedOnVowel(char a) {
+  if (isVowel(a))
+    return "an";
+  return "a";
+}
 } // end of namespace clang::ento::tagged_union_modeling
 
 static std::optional<ArrayRef<TemplateArgument>>
@@ -108,25 +128,6 @@ getNthTemplateTypeArgFromVariant(const Type *varType, unsigned i) {
   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{{"std", "variant", "variant"}};
@@ -184,9 +185,8 @@ class StdVariantChecker : public Checker<eval::Call, check::RegionChanges> {
       } else if (IsVariantAssignmentOperatorCall) {
         const auto &AsMemberOpCall = cast<CXXMemberOperatorCall>(Call);
         ThisSVal = AsMemberOpCall.getCXXThisVal();
-      } else {
+      } else
         return false;
-      }
 
       handleConstructorAndAssignment<VariantHeldTypeMap>(Call, C, ThisSVal);
       return true;
diff --git a/clang/lib/StaticAnalyzer/Checkers/TaggedUnionModeling.h b/clang/lib/StaticAnalyzer/Checkers/TaggedUnionModeling.h
index 557e8a76506e61..3b376777ae16c2 100644
--- a/clang/lib/StaticAnalyzer/Checkers/TaggedUnionModeling.h
+++ b/clang/lib/StaticAnalyzer/Checkers/TaggedUnionModeling.h
@@ -9,15 +9,9 @@
 #ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_TAGGEDUNIONMODELING_H
 #define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_TAGGEDUNIONMODELING_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 <numeric>
 
 namespace clang::ento::tagged_union_modeling {
 
@@ -30,6 +24,9 @@ bool isMoveAssignmentCall(const CallEvent &Call);
 bool isMoveConstructorCall(const CallEvent &Call);
 bool isStdType(const Type *Type, const std::string &TypeName);
 bool isStdVariant(const Type *Type);
+bool isStdAny(const Type *Type);
+bool isVowel(char a);
+llvm::StringRef indefiniteArticleBasedOnVowel(char a);
 
 // When invalidating regions, we also have to follow that by invalidating the
 // corresponding custom data in the program state.
diff --git a/clang/test/Analysis/Inputs/system-header-simulator-cxx.h b/clang/test/Analysis/Inputs/system-header-simulator-cxx.h
index 3ef7af2ea6c6ab..e1ad590526eb38 100644
--- a/clang/test/Analysis/Inputs/system-header-simulator-cxx.h
+++ b/clang/test/Analysis/Inputs/system-header-simulator-cxx.h
@@ -1372,6 +1372,65 @@ template <typename Ret, typename... Args> class packaged_task<Ret(Args...)> {
             typename = std::enable_if_t<!is_same_v<std::variant<Types...>, decay_t<T>>>>
     variant& operator=(T&&);
   };
+
+  class bad_any_cast;
+ 
+  // class any
+  class any;
+ 
+  // non-member functions
+  void swap(any& x, any& y) noexcept;
+ 
+  template<class T, class... Args>
+    any make_any(Args&&... args);
+  template<class T, class U, class... Args>
+    any make_any(initializer_list<U> il, Args&&... args);
+ 
+  template<class T>
+    T any_cast(const any& operand);
+  template<class T>
+    T any_cast(any& operand);
+  template<class T>
+    T any_cast(any&& operand);
+ 
+  template<class T>
+    const T* any_cast(const any* operand) noexcept;
+  template<class T>
+    T* any_cast(any* operand) noexcept;
+
+  class any {
+  public:
+    // construction and destruction
+    constexpr any() noexcept;
+ 
+    any(const any& other);
+    any(any&& other) noexcept;
+ 
+    template<class T>
+    any(T&& value);
+ 
+    ~any();
+ 
+    // assignments
+    any& operator=(const any& rhs);    
+    any& operator=(any&& rhs) noexcept;
+ 
+    template<typename T,
+            typename = std::enable_if_t<!is_same_v<std::any, decay_t<T>>>>
+    any& operator=(T&& rhs);
+ 
+    // modifiers
+    template<class T, class... Args>
+      decay_t<T>& emplace(Args&&...);
+    template<class T, class U, class... Args>
+      decay_t<T>& emplace(initializer_list<U>, Args&&...);
+    void reset() noexcept;
+    void swap(any& rhs) noexcept;
+ 
+    // observers
+    bool has_value() const noexcept;
+  };
+
   #endif
 
 } // namespace std
diff --git a/clang/test/Analysis/std-any-checker.cpp b/clang/test/Analysis/std-any-checker.cpp
new file mode 100644
index 00000000000000..d28d311b32d843
--- /dev/null
+++ b/clang/test/Analysis/std-any-checker.cpp
@@ -0,0 +1,170 @@
+// RUN: %clang %s -Xclang -verify --analyze \
+// RUN:   -Xclang -analyzer-checker=core \
+// RUN:   -Xclang -analyzer-checker=debug.ExprInspection \
+// RUN:   -Xclang -analyzer-checker=core,alpha.core.StdAny
+
+#include "Inputs/system-header-simulator-cxx.h"
+
+void clang_analyzer_warnIfReached();
+void clang_analyzer_eval(int);
+
+
+class DummyClass{
+  public:
+  void foo(){};
+};
+
+void nonInlined(std::any &a);
+void nonInlinedConst(const std::any & a);
+
+void inlined(std::any &a) {
+  a = 5;
+}
+
+using any_t = std::any;
+using any_tt = any_t;
+
+
+//----------------------------------------------------------------------------//
+// std::any_cast
+//----------------------------------------------------------------------------//
+void objectHeld() {
+  std::any a = DummyClass{};
+  DummyClass d = std::any_cast<DummyClass>(a);
+  d.foo();
+}
+
+void formVariable() {
+  std::any a = 5;
+  int b = std::any_cast<int>(a);
+  char c = std::any_cast<char>(a); // expected-warning {{std::any 'a' held an 'int', not a 'char'}}
+  (void)b;
+  (void)c;
+}
+
+void pointerHeld() {
+  int i = 5;
+  std::any a = &i;
+  int* x = std::any_cast<int*>(a);
+  char c = std::any_cast<char>(a); // expected-warning {{std::any 'a' held an 'int *', not a 'char'}}
+  (void)x;
+  (void)c;
+}
+
+//----------------------------------------------------------------------------//
+// Empty std::any
+//----------------------------------------------------------------------------//
+
+void noTypeHeld() {
+  std::any a;
+  int i = std::any_cast<int>(a); // expected-warning {{any 'a' is empty}}
+  (void)i;
+}
+
+void reset() {
+  std::any a = 15;
+  a.reset();
+  int i = std::any_cast<int>(a); // expected-warning {{any 'a' is empty}}
+  (void)i;
+}
+
+
+//----------------------------------------------------------------------------//
+// Typedefs
+//----------------------------------------------------------------------------//
+
+void typedefedAny () {
+  any_t a = 5;
+  int i = std::any_cast<int>(a);
+  char c = std::any_cast<char>(a); // expected-warning {{std::any 'a' held an 'int', not a 'char'}}
+  (void)i;
+  (void)c;
+}
+
+void typedefedTypedefedAny () {
+  any_tt a = 5;
+  int i = std::any_cast<int>(a);
+  char c = std::any_cast<char>(a); // expected-warning {{std::any 'a' held an 'int', not a 'char'}}
+  (void)i;
+  (void)c;
+}
+
+//----------------------------------------------------------------------------//
+// Constructors and assignments
+//----------------------------------------------------------------------------//
+
+void assignmentOp () {
+  std::any a;
+  a = 5;
+  int i = std::any_cast<int>(a);
+  char c = std::any_cast<char>(a); // expected-warning {{std::any 'a' held an 'int', not a 'char'}}
+  (void)i;
+  (void)c;
+}
+
+void constructor() {
+  std::any a(5);
+  int i = std::any_cast<int>(a);
+  char c = std::any_cast<char>(a); // expected-warning {{std::any 'a' held an 'int', not a 'char'}}
+  (void)i;
+  (void)c;
+}
+
+void copyCtor() {
+  std::any a(5);
+  std::any b(a);
+  int i = std::any_cast<int>(a);
+  char c = std::any_cast<char>(a); // expected-warning {{std::any 'a' held an 'int', not a 'char'}}
+  (void)i;
+  (void)c;
+}
+
+void copyCtorNullType() {
+  std::any a;
+  std::any b(a);
+  char c = std::any_cast<char>(a); // expected-warning {{any 'a' is empty}}
+  (void)c;
+}
+
+void copyAssignment() {
+  std::any a = 5;
+  std::any b = 'c';
+  char c = std::any_cast<char>(b);
+  (void)c;
+  b = a;
+  int i = std::any_cast<int>(b);
+  c = std::any_cast<char>(b); // expected-warning {{std::any 'b' held an 'int', not a 'char'}}
+  (void)i;
+  (void)c;
+}
+
+//----------------------------------------------------------------------------//
+// Function calls
+//----------------------------------------------------------------------------//
+
+void nonInlinedRefCall() {
+  std::any a = 5;
+  nonInlined(a);
+  int i = std::any_cast<int>(a);
+  char c = std::any_cast<char>(a);
+  (void)i;
+  (void)c;
+}
+
+void nonInlinedConstRefCall() {
+  std::any a = 5;
+  nonInlinedConst(a);
+  int i = std::any_cast<int>(a);
+  char c = std::any_cast<char>(a); // expected-warning {{std::any 'a' held an 'int', not a 'char'}}
+  (void)i;
+  (void)c;
+}
+
+void inlinedCall() {
+  std::any a = 'c';
+  inlined(a);
+  int i = std::any_cast<int>(a);
+  char c = std::any_cast<char>(a); // expected-warning {{std::any 'a' held an 'int', not a 'char'}}
+  (void)i;
+  (void)c;
+}
\ No newline at end of file
diff --git a/clang/test/Analysis/std-variant-checker.cpp b/clang/test/Analysis/std-variant-checker.cpp
index 7f136c06b19cc6..bdae26742826af 100644
--- a/clang/test/Analysis/std-variant-checker.cpp
+++ b/clang/test/Analysis/std-variant-checker.cpp
@@ -58,6 +58,14 @@ void wontConfuseStdGets() {
 //----------------------------------------------------------------------------//
 // std::get
 //----------------------------------------------------------------------------//
+void stdGetType2() {
+  std::variant<int, char> v = {25};
+  int a = std::get<int>(v);
+  char c = std::get<char>(v); // expected-warning {{std::variant 'v' held an 'int', not a 'char'}}
+  (void)a;
+  (void)c;
+}
+
 void stdGetType() {
   std::variant<int, char> v = 25;
   int a = std::get<int>(v);



More information about the cfe-commits mailing list