[clang] [analyzer] Add std::variant checker (PR #66481)
Gábor Spaits via cfe-commits
cfe-commits at lists.llvm.org
Fri Sep 15 05:58:09 PDT 2023
https://github.com/spaits updated https://github.com/llvm/llvm-project/pull/66481
>From 5472e377f9a4b51a8c400f1cc1703aa83fa45d1b Mon Sep 17 00:00:00 2001
From: Gabor Spaits <gaborspaits1 at gmail.com>
Date: Fri, 15 Sep 2023 10:21:30 +0200
Subject: [PATCH] [analyzer] Add std::variant checker
Adding a checker that checks for bad std::variant type access.
---
.../clang/StaticAnalyzer/Checkers/Checkers.td | 4 +
.../StaticAnalyzer/Checkers/CMakeLists.txt | 1 +
.../Checkers/StdVariantChecker.cpp | 312 +++++++++++++++++
.../Checkers/TaggedUnionModeling.h | 128 +++++++
.../Inputs/system-header-simulator-cxx.h | 117 +++++++
.../diagnostics/explicit-suppression.cpp | 2 +-
clang/test/Analysis/std-variant-checker.cpp | 326 ++++++++++++++++++
7 files changed, 889 insertions(+), 1 deletion(-)
create mode 100644 clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp
create mode 100644 clang/lib/StaticAnalyzer/Checkers/TaggedUnionModeling.h
create mode 100644 clang/test/Analysis/std-variant-checker.cpp
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
index 65c1595eb6245dd..ec77ba1dc474d60 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -318,6 +318,10 @@ def C11LockChecker : Checker<"C11Lock">,
Dependencies<[PthreadLockBase]>,
Documentation<HasDocumentation>;
+def StdVariantChecker : Checker<"StdVariant">,
+ HelpText<"Check wether we access the right type stored in std::variant">,
+ Documentation<NotDocumented>;
+
} // end "alpha.core"
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
index ae849f59f90d3d9..d7cb51e1a0819a8 100644
--- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -108,6 +108,7 @@ add_clang_library(clangStaticAnalyzerCheckers
SmartPtrModeling.cpp
StackAddrEscapeChecker.cpp
StdLibraryFunctionsChecker.cpp
+ StdVariantChecker.cpp
STLAlgorithmModeling.cpp
StreamChecker.cpp
StringChecker.cpp
diff --git a/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp
new file mode 100644
index 000000000000000..314dab24650dcd2
--- /dev/null
+++ b/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp
@@ -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;
+ }
+ SVal thisSVal = [&]() {
+ if (IsVariantConstructor) {
+ const auto *AsConstructorCall = dyn_cast<CXXConstructorCall>(&Call);
+ return AsConstructorCall->getCXXThisVal();
+ }
+ if (IsVariantAssignmentOperatorCall) {
+ const auto *AsMemberOpCall = dyn_cast<CXXMemberOperatorCall>(&Call);
+ return AsMemberOpCall->getCXXThisVal();
+ }
+ llvm_unreachable("We must have an assignment operator or constructor");
+ }();
+ 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 CallEvent &Call,
+ CheckerContext &C) const {
+
+ const auto *AsConstructorCall = dyn_cast<CXXConstructorCall>(&Call);
+ assert(AsConstructorCall && "A constructor call must be passed!");
+
+ SVal ThisSVal = AsConstructorCall->getCXXThisVal();
+
+ const auto *const ThisMemRegion = ThisSVal.getAsRegion();
+ if (!ThisMemRegion) {
+ return;
+ }
+
+ QualType DefaultType =
+ getNthTemplateTypeArgFromVariant(ThisSVal.getType(C.getASTContext())
+ .getTypePtr()
+ ->getPointeeType()
+ .getTypePtr(),
+ 0);
+
+ ProgramStateRef State = Call.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())
+ .getTypePtr()
+ ->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 get what type
+ // information is known about it.
+ const MemRegion *ArgMemRegion = Call.getArgSVal(0).getAsRegion();
+ const QualType *StoredType = State->get<VariantHeldTypeMap>(ArgMemRegion);
+ if (!StoredType) {
+ return false;
+ }
+
+ const auto &TypeOut = getFirstTemplateArgument(Call);
+ // 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 wished type in the argument std::variant's type list.
+ auto RetrievedType = [&]() {
+ switch (TypeOut.getKind()) {
+ case TemplateArgument::ArgKind::Type:
+ return TypeOut.getAsType();
+ case TemplateArgument::ArgKind::Integral:
+ // In the natural number case we look up which type corresponds to the
+ // number.
+ return getNthTemplateTypeArgFromVariant(
+ ArgType, TypeOut.getAsIntegral().getSExtValue());
+ default:
+ llvm_unreachable("An std::get's first template argument can only be a "
+ "type or an integral");
+ }
+ }();
+
+ 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);
+ OS << "std::variant " << ArgMemRegion->getDescriptiveName() << " held a(n) "
+ << StoredType->getAsString() << " not a(n) "
+ << RetrievedType.getAsString();
+ 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>();
+}
\ No newline at end of file
diff --git a/clang/lib/StaticAnalyzer/Checkers/TaggedUnionModeling.h b/clang/lib/StaticAnalyzer/Checkers/TaggedUnionModeling.h
new file mode 100644
index 000000000000000..6c273b345d04635
--- /dev/null
+++ b/clang/lib/StaticAnalyzer/Checkers/TaggedUnionModeling.h
@@ -0,0 +1,128 @@
+//===- TaggedUnionModeling.h -------------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKER_VARIANTLIKETYPEMODELING_H
+#define LLVM_CLANG_LIB_STATICANALYZER_CHECKER_VARIANTLIKETYPEMODELING_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 {
+namespace ento {
+namespace variant_modeling {
+
+// The implementation of all these functions can be found in the
+// StdVariantChecker.cpp file under the same directory as this file.
+CallEventRef<> getCaller(const CallEvent &Call, CheckerContext &C);
+const TemplateArgument &getFirstTemplateArgument(const CallEvent &Call);
+bool isCopyConstructorCall(const CallEvent &Call);
+bool isCopyAssignmentCall(const CallEvent &Call);
+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 calledFromSystemHeader(const CallEvent &Call, CheckerContext &C);
+
+// When invalidating regions we also have to follow that with our data
+// storages in the program state.
+template <class TypeMap>
+ProgramStateRef
+removeInformationStoredForDeadInstances(const CallEvent *Call,
+ ProgramStateRef State,
+ ArrayRef<const MemRegion *> Regions) {
+ // If we do not know anything about the call we shall not continue.
+ if (!Call) {
+ return State;
+ }
+
+ // If the call is coming from a system header it is implementation detail.
+ // We should not take it into consideration.
+ if (Call->isInSystemHeader()) {
+ return State;
+ }
+
+ // Remove the information we know about the invalidate region.
+ // It is not relevant anymore.
+ State = std::accumulate(
+ Regions.begin(), Regions.end(), State,
+ [](ProgramStateRef State, const MemRegion *CurrentMemRegion) {
+ if (State->contains<TypeMap>(CurrentMemRegion)) {
+ State = State->remove<TypeMap>(CurrentMemRegion);
+ }
+ return State;
+ });
+ return State;
+}
+
+template <class TypeMap>
+void handleConstructorAndAssignment(const CallEvent &Call, CheckerContext &C,
+ const SVal &ThisSVal) {
+ ProgramStateRef State = Call.getState();
+
+ auto ArgSVal = Call.getArgSVal(0);
+ const auto *ThisRegion = ThisSVal.getAsRegion();
+ const auto *ArgMemRegion = ArgSVal.getAsRegion();
+
+ // Make changes to the state according to type of constructor/assignment
+ State = [&]() {
+ bool IsCopy = isCopyConstructorCall(Call) || isCopyAssignmentCall(Call);
+ bool IsMove = isMoveConstructorCall(Call) || isMoveAssignmentCall(Call);
+
+ // First we handle copy and move operations
+ if (IsCopy || IsMove) {
+ // If the argument of a copy constructor or assignment is unknown then
+ // we will not know the argument of the copied to object.
+ bool OtherQTypeKnown = State->contains<TypeMap>(ArgMemRegion);
+
+ const QualType *OtherQType;
+ if (OtherQTypeKnown) {
+ OtherQType = State->get<TypeMap>(ArgMemRegion);
+ } else {
+ return State->contains<TypeMap>(ThisRegion)
+ ? State->remove<TypeMap>(ThisRegion)
+ : State;
+ }
+
+ // When move semantics is used we can only know that the moved from
+ // object must be in a destructible state. Other usage of the object
+ // than destruction is undefined.
+ if (IsMove) {
+ State = State->contains<TypeMap>(ArgMemRegion)
+ ? State->remove<TypeMap>(ArgMemRegion)
+ : State;
+ }
+ return State->set<TypeMap>(ThisRegion, *OtherQType);
+ }
+ // Then the other constructor/assignment where the argument is the new
+ // object held by the std::variant or std::any
+ auto ArgQType = ArgSVal.getType(C.getASTContext());
+ const Type *ArgTypePtr = ArgQType.getTypePtr();
+
+ QualType WoPointer = ArgTypePtr->getPointeeType();
+ return State->set<TypeMap>(ThisRegion, WoPointer);
+ }();
+
+ if (State) {
+ C.addTransition(State);
+ } else {
+ C.addTransition(Call.getState()->remove<TypeMap>(ThisRegion));
+ }
+}
+
+} // namespace variant_modeling
+} // namespace ento
+} // namespace clang
+
+#endif // LLVM_CLANG_LIB_STATICANALYZER_CHECKER_VARIANTLIKETYPEMODELING_H
\ No newline at end of file
diff --git a/clang/test/Analysis/Inputs/system-header-simulator-cxx.h b/clang/test/Analysis/Inputs/system-header-simulator-cxx.h
index 8633a8beadbff33..b1cb829268e3cb8 100644
--- a/clang/test/Analysis/Inputs/system-header-simulator-cxx.h
+++ b/clang/test/Analysis/Inputs/system-header-simulator-cxx.h
@@ -264,6 +264,9 @@ namespace std {
return static_cast<RvalRef>(a);
}
+ template< class T >
+ using remove_reference_t = typename remove_reference<T>::type;
+
template <class T>
void swap(T &a, T &b) {
T c(std::move(a));
@@ -718,6 +721,11 @@ namespace std {
template <class _Tp, class _Up> struct is_same : public false_type {};
template <class _Tp> struct is_same<_Tp, _Tp> : public true_type {};
+ #if __cplusplus >= 201703L
+ template< class T, class U >
+ inline constexpr bool is_same_v = is_same<T, U>::value;
+ #endif
+
template <class _Tp, bool = is_const<_Tp>::value || is_reference<_Tp>::value >
struct __add_const {typedef _Tp type;};
@@ -729,6 +737,9 @@ namespace std {
template <class _Tp> struct remove_const {typedef _Tp type;};
template <class _Tp> struct remove_const<const _Tp> {typedef _Tp type;};
+ template< class T >
+ using remove_const_t = typename remove_const<T>::type;
+
template <class _Tp> struct add_lvalue_reference {typedef _Tp& type;};
template <class _Tp> struct is_trivially_copy_assignable
@@ -793,6 +804,9 @@ namespace std {
return __result;
}
+ template< bool B, class T = void >
+ using enable_if_t = typename enable_if<B,T>::type;
+
template<class InputIter, class OutputIter>
OutputIter copy_backward(InputIter II, InputIter IE, OutputIter OI) {
return __copy_backward(II, IE, OI);
@@ -1252,4 +1266,107 @@ template <typename Ret, typename... Args> class packaged_task<Ret(Args...)> {
// TODO: Add some actual implementation.
};
+ #if __cplusplus >= 201703L
+
+ namespace detail
+ {
+ template<class T>
+ struct type_identity { using type = T; }; // or use std::type_identity (since C++20)
+
+ template<class T>
+ auto try_add_pointer(int) -> type_identity<typename std::remove_reference<T>::type*>;
+ template<class T>
+ auto try_add_pointer(...) -> type_identity<T>;
+ } // namespace detail
+
+ template<class T>
+ struct add_pointer : decltype(detail::try_add_pointer<T>(0)) {};
+
+ template< class T >
+ using add_pointer_t = typename add_pointer<T>::type;
+
+ template<class T> struct remove_cv { typedef T type; };
+ template<class T> struct remove_cv<const T> { typedef T type; };
+ template<class T> struct remove_cv<volatile T> { typedef T type; };
+ template<class T> struct remove_cv<const volatile T> { typedef T type; };
+
+ template< class T >
+ using remove_cv_t = typename remove_cv<T>::type;
+
+ // This decay does not behave exactly like std::decay, but this is enough
+ // for testing the std::variant checker
+ template<class T>
+ struct decay{typedef remove_cv_t<remove_reference_t<T>> type;};
+ template<class T>
+ using decay_t = typename decay<T>::type;
+
+ // variant
+ template <class... Types> class variant;
+ // variant helper classes
+ template <class T> struct variant_size;
+ template <class T> struct variant_size<const T>;
+ template <class T> struct variant_size<volatile T>;
+ template <class T> struct variant_size<const volatile T>;
+ template <class T> inline constexpr size_t variant_size_v = variant_size<T>::value;
+ template <class... Types>
+ struct variant_size<variant<Types...>>;
+ template <size_t I, class T> struct variant_alternative;
+ template <size_t I, class T> struct variant_alternative<I, const T>;
+ template <size_t I, class T> struct variant_alternative<I, volatile T>;
+ template <size_t I, class T> struct variant_alternative<I, const volatile T>;
+ template <size_t I, class T>
+ using variant_alternative_t = typename variant_alternative<I, T>::type;
+ template <size_t I, class... Types>
+ struct variant_alternative<I, variant<Types...>>;
+ inline constexpr size_t variant_npos = -1;
+ template <size_t I, class... Types>
+ constexpr variant_alternative_t<I, variant<Types...>>&
+ get(variant<Types...>&);
+ template <size_t I, class... Types>
+ constexpr variant_alternative_t<I, variant<Types...>>&&
+ get(variant<Types...>&&);
+ template <size_t I, class... Types>
+ constexpr const variant_alternative_t<I, variant<Types...>>&
+ get(const variant<Types...>&);
+ template <size_t I, class... Types>
+ constexpr const variant_alternative_t<I, variant<Types...>>&&
+ get(const variant<Types...>&&);
+ template <class T, class... Types>
+ constexpr T& get(variant<Types...>&);
+ template <class T, class... Types>
+ constexpr T&& get(variant<Types...>&&);
+ template <class T, class... Types>
+ constexpr const T& get(const variant<Types...>&);
+ template <class T, class... Types>
+ constexpr const T&& get(const variant<Types...>&&);
+ template <size_t I, class... Types>
+ constexpr add_pointer_t<variant_alternative_t<I, variant<Types...>>>
+ get_if(variant<Types...>*) noexcept;
+ template <size_t I, class... Types>
+ constexpr add_pointer_t<const variant_alternative_t<I, variant<Types...>>>
+ get_if(const variant<Types...>*) noexcept;
+ template <class T, class... Types>
+ constexpr add_pointer_t<T> get_if(variant<Types...>*) noexcept;
+ template <class T, class... Types>
+ constexpr add_pointer_t<const T> get_if(const variant<Types...>*) noexcept;
+
+ template <class... Types>
+ class variant {
+ public:
+ // constructors
+ constexpr variant()= default ;
+ constexpr variant(const variant&);
+ constexpr variant(variant&&);
+ template<typename T,
+ typename = std::enable_if_t<!is_same_v<std::variant<Types...>, decay_t<T>>>>
+ constexpr variant(T&&);
+ // assignment
+ variant& operator=(const variant&);
+ variant& operator=(variant&&) ;
+ template<typename T,
+ typename = std::enable_if_t<!is_same_v<std::variant<Types...>, decay_t<T>>>>
+ variant& operator=(T&&);
+ };
+ #endif
+
} // namespace std
diff --git a/clang/test/Analysis/diagnostics/explicit-suppression.cpp b/clang/test/Analysis/diagnostics/explicit-suppression.cpp
index b98d0260b096594..ce1a02d1be0e500 100644
--- a/clang/test/Analysis/diagnostics/explicit-suppression.cpp
+++ b/clang/test/Analysis/diagnostics/explicit-suppression.cpp
@@ -19,6 +19,6 @@ class C {
void testCopyNull(C *I, C *E) {
std::copy(I, E, (C *)0);
#ifndef SUPPRESSED
- // expected-warning at ../Inputs/system-header-simulator-cxx.h:741 {{Called C++ object pointer is null}}
+ // expected-warning at ../Inputs/system-header-simulator-cxx.h:752 {{Called C++ object pointer is null}}
#endif
}
diff --git a/clang/test/Analysis/std-variant-checker.cpp b/clang/test/Analysis/std-variant-checker.cpp
new file mode 100644
index 000000000000000..3d53250b2447958
--- /dev/null
+++ b/clang/test/Analysis/std-variant-checker.cpp
@@ -0,0 +1,326 @@
+// RUN: %clang %s -std=c++17 -Xclang -verify --analyze \
+// RUN: -Xclang -analyzer-checker=core \
+// RUN: -Xclang -analyzer-checker=debug.ExprInspection \
+// RUN: -Xclang -analyzer-checker=core,alpha.core.StdVariant
+
+#include "Inputs/system-header-simulator-cxx.h"
+
+class Foo{};
+
+void clang_analyzer_warnIfReached();
+void clang_analyzer_eval(int);
+
+//helper functions
+void changeVariantType(std::variant<int, char> &v) {
+ v = 25;
+}
+
+void changesToInt(std::variant<int, char> &v);
+void changesToInt(std::variant<int, char> *v);
+
+void cannotChangePtr(const std::variant<int, char> &v);
+void cannotChangePtr(const std::variant<int, char> *v);
+
+char getUnknownChar();
+
+void swap(std::variant<int, char> &v1, std::variant<int, char> &v2) {
+ std::variant<int, char> tmp = v1;
+ v1 = v2;
+ v2 = tmp;
+}
+
+void cantDo(const std::variant<int, char>& v) {
+ std::variant<int, char> vtmp = v;
+ vtmp = 5;
+ int a = std::get<int> (vtmp);
+ (void*) a;
+}
+
+void changeVariantPtr(std::variant<int, char> *v) {
+ *v = 'c';
+}
+
+using var_t = std::variant<int, char>;
+using var_tt = var_t;
+using int_t = int;
+using char_t = char;
+
+
+//----------------------------------------------------------------------------//
+// std::get
+//----------------------------------------------------------------------------//
+void stdGetType() {
+ std::variant<int, char> v = 25;
+ int a = std::get<int>(v);
+ char c = std::get<char>(v); // expected-warning {{std::variant 'v' held a(n) int not a(n) char}}
+ (void*)a;
+ (void*)c;
+}
+
+void stdGetPointer() {
+ int *p = new int;
+ std::variant<int*, char> v = p;
+ int *a = std::get<int*>(v);
+ char c = std::get<char>(v); // expected-warning {{std::variant 'v' held a(n) int * not a(n) char}}
+ (void**)a;
+ (void*)c;
+ delete p;
+}
+
+void stdGetObject() {
+ std::variant<int, char, Foo> v = Foo{};
+ Foo f = std::get<Foo>(v);
+ int i = std::get<int>(v); // expected-warning {{std::variant 'v' held a(n) Foo not a(n) int}}
+ (void*)i;
+}
+
+void stdGetPointerAndPointee() {
+ int a = 5;
+ std::variant<int, int*> v = &a;
+ int *b = std::get<int*>(v);
+ int c = std::get<int>(v); // expected-warning {{std::variant 'v' held a(n) int * not a(n) int}}
+ (void*)c;
+ (void**)b;
+}
+
+//----------------------------------------------------------------------------//
+// Constructors and assignments
+//----------------------------------------------------------------------------//
+void copyConstructor() {
+ std::variant<int, char> v = 25;
+ std::variant<int, char> t(v);
+ int a = std::get<int> (t);
+ char c = std::get<char> (t); // expected-warning {{std::variant 't' held a(n) int not a(n) char}}
+ (void*)a;
+ (void*)c;
+}
+
+void copyAssignmentOperator() {
+ std::variant<int, char> v = 25;
+ std::variant<int, char> t = 'c';
+ t = v;
+ int a = std::get<int> (t);
+ char c = std::get<char> (t); // expected-warning {{std::variant 't' held a(n) int not a(n) char}}
+ (void*)a;
+ (void*)c;
+}
+
+void assignmentOperator() {
+ std::variant<int, char> v = 25;
+ int a = std::get<int> (v);
+ (void*)a;
+ v = 'c';
+ char c = std::get<char>(v);
+ a = std::get<int>(v); // expected-warning {{std::variant 'v' held a(n) char not a(n) int}}
+ (void*)a;
+ (void*)c;
+}
+
+void defaultConstructor() {
+ std::variant<int, char> v;
+ int i = std::get<int>(v);
+ char c = std::get<char>(v); // expected-warning {{std::variant 'v' held a(n) int not a(n) char}}
+ (void*)i;
+ (void*)c;
+}
+
+// Verify that we handle temporary objects correctly
+void temporaryObjectsConstructor() {
+ std::variant<int, char> v(std::variant<int, char>('c'));
+ char c = std::get<char>(v);
+ int a = std::get<int>(v); // expected-warning {{std::variant 'v' held a(n) char not a(n) int}}
+ (void*)a;
+ (void*)c;
+}
+
+void temporaryObjectsAssignment() {
+ std::variant<int, char> v = std::variant<int, char>('c');
+ char c = std::get<char>(v);
+ int a = std::get<int>(v); // expected-warning {{std::variant 'v' held a(n) char not a(n) int}}
+ (void*)a;
+ (void*)c;
+}
+
+// Verify that we handle pointer types correctly
+void pointerTypeHeld() {
+ int *p = new int;
+ std::variant<int*, char> v = p;
+ int *a = std::get<int*>(v);
+ char c = std::get<char>(v); // expected-warning {{std::variant 'v' held a(n) int * not a(n) char}}
+ (void**)a;
+ (void*)c;
+ delete p;
+}
+
+std::variant<int, char> get_unknown_variant();
+// Verify that the copy constructor is handles properly when the std::variant
+// has no previously activated type and we copy an object of unknown value in it.
+void copyFromUnknownVariant() {
+ std::variant<int, char> u = get_unknown_variant();
+ std::variant<int, char> v(u);
+ int a = std::get<int>(v); // no-waring
+ char c = std::get<char>(v); // no-warning
+ (void*)a;
+ (void*)c;
+}
+
+// Verify that the copy constructor is handles properly when the std::variant
+// has previously activated type and we copy an object of unknown value in it.
+void copyFromUnknownVariantBef() {
+ std::variant<int, char> v = 25;
+ std::variant<int, char> u = get_unknown_variant();
+ v = u;
+ int a = std::get<int>(v); // no-waring
+ char c = std::get<char>(v); // no-warning
+ (void*)a;
+ (void*)c;
+}
+
+//----------------------------------------------------------------------------//
+// typedef
+//----------------------------------------------------------------------------//
+
+void typefdefedVariant() {
+ var_t v = 25;
+ int a = std::get<int>(v);
+ char c = std::get<char>(v); // expected-warning {{std::variant 'v' held a(n) int not a(n) char}}
+ (void*)a;
+ (void*)c;
+}
+
+void typedefedTypedfefedVariant() {
+ var_tt v = 25;
+ int a = std::get<int>(v);
+ char c = std::get<char>(v); // expected-warning {{std::variant 'v' held a(n) int not a(n) char}}
+ (void*)a;
+ (void*)c;
+}
+
+void typedefedGet() {
+ std::variant<char, int> v = 25;
+ int a = std::get<int_t>(v);
+ char c = std::get<char_t>(v); // expected-warning {{std::variant 'v' held a(n) int not a(n) char}}
+ (void*)a;
+ (void*)c;
+}
+
+void typedefedPack() {
+ std::variant<int_t, char_t> v = 25;
+ int a = std::get<int>(v);
+ char c = std::get<char>(v); // expected-warning {{std::variant 'v' held a(n) int not a(n) char}}
+ (void*)a;
+ (void*)c;
+}
+
+void fromVariable() {
+ char o = 'c';
+ std::variant<int, char> v(o);
+ char c = std::get<char>(v);
+ int a = std::get<int>(v); // expected-warning {{std::variant 'v' held a(n) char not a(n) int}}
+ (void*)a;
+ (void*)c;
+}
+
+void unknowValueButKnownType() {
+ char o = getUnknownChar();
+ std::variant<int, char> v(o);
+ char c = std::get<char>(v);
+ int a = std::get<int>(v); // expected-warning {{std::variant 'v' held a(n) char not a(n) int}}
+ (void*)a;
+ (void*)c;
+}
+
+void createPointer() {
+ std::variant<int, char> *v = new std::variant<int, char>(15);
+ int a = std::get<int>(*v);
+ char c = std::get<char>(*v); // expected-warning {{std::variant held a(n) int not a(n) char}}
+ (void*)a;
+ (void*)c;
+ delete v;
+}
+
+//----------------------------------------------------------------------------//
+// Passing std::variants to functions
+//----------------------------------------------------------------------------//
+
+// Verifying that we are not invalidating the memory region of a variant if
+// a non inlined or inlined function takes it as a constant reference or pointer
+void constNonInlineRef() {
+ std::variant<int, char> v = 'c';
+ cannotChangePtr(v);
+ char c = std::get<char>(v);
+ int a = std::get<int>(v); // expected-warning {{std::variant 'v' held a(n) char not a(n) int}}
+ (void*)a;
+ (void*)c;
+}
+
+void contNonInlinePtr() {
+ std::variant<int, char> v = 'c';
+ cannotChangePtr(&v);
+ char c = std::get<char>(v);
+ int a = std::get<int>(v); // expected-warning {{std::variant 'v' held a(n) char not a(n) int}}
+ (void*)a;
+ (void*)c;
+}
+
+void copyInAFunction() {
+ std::variant<int, char> v = 'c';
+ cantDo(v);
+ char c = std::get<char>(v);
+ int a = std::get<int>(v); // expected-warning {{std::variant 'v' held a(n) char not a(n) int}}
+ (void*)a;
+ (void*)c;
+
+}
+
+// Verifying that we can keep track of the type stored in std::variant when
+// it is passed to an inlined function as a reference or pointer
+void changeThruPointers() {
+ std::variant<int, char> v = 15;
+ changeVariantPtr(&v);
+ char c = std::get<char> (v);
+ int a = std::get<int> (v); // expected-warning {{std::variant 'v' held a(n) char not a(n) int}}
+ (void*)a;
+ (void*)c;
+}
+
+void functionCallWithCopyAssignment() {
+ var_t v1 = 15;
+ var_t v2 = 'c';
+ swap(v1, v2);
+ int a = std::get<int> (v2);
+ (void*)a;
+ char c = std::get<char> (v1);
+ a = std::get<int> (v1); // expected-warning {{std::variant 'v1' held a(n) char not a(n) int}}
+ (void*)a;
+ (void*)c;
+}
+
+void inlineFunctionCall() {
+ std::variant<int, char> v = 'c';
+ changeVariantType(v);
+ int a = std::get<int> (v);
+ char c = std::get<char> (v); // expected-warning {{std::variant 'v' held a(n) int not a(n) char}}
+ (void*)a;
+ (void*)c;
+}
+
+// Verifying that we invalidate the mem region of std::variant when it is
+// passed as a non const reference or a pointer to a non inlined function.
+void nonInlineFunctionCall() {
+ std::variant<int, char> v = 'c';
+ changesToInt(v);
+ int a = std::get<int> (v); // no-waring
+ char c = std::get<char> (v); // no-warning
+ (void*)a;
+ (void*)c;
+}
+
+void nonInlineFunctionCallPtr() {
+ std::variant<int, char> v = 'c';
+ changesToInt(&v);
+ int a = std::get<int> (v); // no-warning
+ char c = std::get<char> (v); // no-warning
+ (void*)a;
+ (void*)c;
+}
\ No newline at end of file
More information about the cfe-commits
mailing list