[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