| //===- UncheckedOptionalAccessModelTest.cpp -------------------------------===// |
| // |
| // 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 |
| // |
| //===----------------------------------------------------------------------===// |
| // FIXME: Move this to clang/unittests/Analysis/FlowSensitive/Models. |
| |
| #include "clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h" |
| #include "TestingSupport.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/ASTMatchers/ASTMatchers.h" |
| #include "clang/Basic/SourceLocation.h" |
| #include "clang/Frontend/TextDiagnostic.h" |
| #include "clang/Tooling/Tooling.h" |
| #include "llvm/ADT/DenseSet.h" |
| #include "llvm/ADT/STLExtras.h" |
| #include "llvm/Support/Error.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include <optional> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| using namespace clang; |
| using namespace dataflow; |
| using namespace test; |
| |
| using ::testing::ContainerEq; |
| |
| // FIXME: Move header definitions in separate file(s). |
| static constexpr char CSDtdDefHeader[] = R"( |
| #ifndef CSTDDEF_H |
| #define CSTDDEF_H |
| |
| namespace std { |
| |
| typedef decltype(sizeof(char)) size_t; |
| |
| using nullptr_t = decltype(nullptr); |
| |
| } // namespace std |
| |
| #endif // CSTDDEF_H |
| )"; |
| |
| static constexpr char StdTypeTraitsHeader[] = R"( |
| #ifndef STD_TYPE_TRAITS_H |
| #define STD_TYPE_TRAITS_H |
| |
| #include "cstddef.h" |
| |
| namespace std { |
| |
| template <typename T, T V> |
| struct integral_constant { |
| static constexpr T value = V; |
| }; |
| |
| using true_type = integral_constant<bool, true>; |
| using false_type = integral_constant<bool, false>; |
| |
| template< class T > struct remove_reference {typedef T type;}; |
| template< class T > struct remove_reference<T&> {typedef T type;}; |
| template< class T > struct remove_reference<T&&> {typedef T type;}; |
| |
| template <class T> |
| using remove_reference_t = typename remove_reference<T>::type; |
| |
| template <class T> |
| struct remove_extent { |
| typedef T type; |
| }; |
| |
| template <class T> |
| struct remove_extent<T[]> { |
| typedef T type; |
| }; |
| |
| template <class T, size_t N> |
| struct remove_extent<T[N]> { |
| typedef T type; |
| }; |
| |
| template <class T> |
| struct is_array : false_type {}; |
| |
| template <class T> |
| struct is_array<T[]> : true_type {}; |
| |
| template <class T, size_t N> |
| struct is_array<T[N]> : true_type {}; |
| |
| template <class> |
| struct is_function : false_type {}; |
| |
| template <class Ret, class... Args> |
| struct is_function<Ret(Args...)> : true_type {}; |
| |
| namespace detail { |
| |
| template <class T> |
| struct type_identity { |
| using type = T; |
| }; // or use type_identity (since C++20) |
| |
| template <class T> |
| auto try_add_pointer(int) -> type_identity<typename 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 <bool B, class T, class F> |
| struct conditional { |
| typedef T type; |
| }; |
| |
| template <class T, class F> |
| struct conditional<false, T, F> { |
| typedef F 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; |
| |
| template <class T> |
| struct decay { |
| private: |
| typedef typename remove_reference<T>::type U; |
| |
| public: |
| typedef typename conditional< |
| is_array<U>::value, typename remove_extent<U>::type*, |
| typename conditional<is_function<U>::value, typename add_pointer<U>::type, |
| typename remove_cv<U>::type>::type>::type type; |
| }; |
| |
| template <bool B, class T = void> |
| struct enable_if {}; |
| |
| template <class T> |
| struct enable_if<true, T> { |
| typedef T type; |
| }; |
| |
| template <bool B, class T = void> |
| using enable_if_t = typename enable_if<B, T>::type; |
| |
| template <class T, class U> |
| struct is_same : false_type {}; |
| |
| template <class T> |
| struct is_same<T, T> : true_type {}; |
| |
| template <class T> |
| struct is_void : is_same<void, typename remove_cv<T>::type> {}; |
| |
| namespace detail { |
| |
| template <class T> |
| auto try_add_lvalue_reference(int) -> type_identity<T&>; |
| template <class T> |
| auto try_add_lvalue_reference(...) -> type_identity<T>; |
| |
| template <class T> |
| auto try_add_rvalue_reference(int) -> type_identity<T&&>; |
| template <class T> |
| auto try_add_rvalue_reference(...) -> type_identity<T>; |
| |
| } // namespace detail |
| |
| template <class T> |
| struct add_lvalue_reference : decltype(detail::try_add_lvalue_reference<T>(0)) { |
| }; |
| |
| template <class T> |
| struct add_rvalue_reference : decltype(detail::try_add_rvalue_reference<T>(0)) { |
| }; |
| |
| template <class T> |
| typename add_rvalue_reference<T>::type declval() noexcept; |
| |
| namespace detail { |
| |
| template <class T> |
| auto test_returnable(int) |
| -> decltype(void(static_cast<T (*)()>(nullptr)), true_type{}); |
| template <class> |
| auto test_returnable(...) -> false_type; |
| |
| template <class From, class To> |
| auto test_implicitly_convertible(int) |
| -> decltype(void(declval<void (&)(To)>()(declval<From>())), true_type{}); |
| template <class, class> |
| auto test_implicitly_convertible(...) -> false_type; |
| |
| } // namespace detail |
| |
| template <class From, class To> |
| struct is_convertible |
| : integral_constant<bool, |
| (decltype(detail::test_returnable<To>(0))::value && |
| decltype(detail::test_implicitly_convertible<From, To>( |
| 0))::value) || |
| (is_void<From>::value && is_void<To>::value)> {}; |
| |
| template <class From, class To> |
| inline constexpr bool is_convertible_v = is_convertible<From, To>::value; |
| |
| template <class...> |
| using void_t = void; |
| |
| template <class, class T, class... Args> |
| struct is_constructible_ : false_type {}; |
| |
| template <class T, class... Args> |
| struct is_constructible_<void_t<decltype(T(declval<Args>()...))>, T, Args...> |
| : true_type {}; |
| |
| template <class T, class... Args> |
| using is_constructible = is_constructible_<void_t<>, T, Args...>; |
| |
| template <class T, class... Args> |
| inline constexpr bool is_constructible_v = is_constructible<T, Args...>::value; |
| |
| template <class _Tp> |
| struct __uncvref { |
| typedef typename remove_cv<typename remove_reference<_Tp>::type>::type type; |
| }; |
| |
| template <class _Tp> |
| using __uncvref_t = typename __uncvref<_Tp>::type; |
| |
| template <bool _Val> |
| using _BoolConstant = integral_constant<bool, _Val>; |
| |
| template <class _Tp, class _Up> |
| using _IsSame = _BoolConstant<__is_same(_Tp, _Up)>; |
| |
| template <class _Tp, class _Up> |
| using _IsNotSame = _BoolConstant<!__is_same(_Tp, _Up)>; |
| |
| template <bool> |
| struct _MetaBase; |
| template <> |
| struct _MetaBase<true> { |
| template <class _Tp, class _Up> |
| using _SelectImpl = _Tp; |
| template <template <class...> class _FirstFn, template <class...> class, |
| class... _Args> |
| using _SelectApplyImpl = _FirstFn<_Args...>; |
| template <class _First, class...> |
| using _FirstImpl = _First; |
| template <class, class _Second, class...> |
| using _SecondImpl = _Second; |
| template <class _Result, class _First, class... _Rest> |
| using _OrImpl = |
| typename _MetaBase<_First::value != true && sizeof...(_Rest) != 0>:: |
| template _OrImpl<_First, _Rest...>; |
| }; |
| |
| template <> |
| struct _MetaBase<false> { |
| template <class _Tp, class _Up> |
| using _SelectImpl = _Up; |
| template <template <class...> class, template <class...> class _SecondFn, |
| class... _Args> |
| using _SelectApplyImpl = _SecondFn<_Args...>; |
| template <class _Result, class...> |
| using _OrImpl = _Result; |
| }; |
| |
| template <bool _Cond, class _IfRes, class _ElseRes> |
| using _If = typename _MetaBase<_Cond>::template _SelectImpl<_IfRes, _ElseRes>; |
| |
| template <class... _Rest> |
| using _Or = typename _MetaBase<sizeof...(_Rest) != |
| 0>::template _OrImpl<false_type, _Rest...>; |
| |
| template <bool _Bp, class _Tp = void> |
| using __enable_if_t = typename enable_if<_Bp, _Tp>::type; |
| |
| template <class...> |
| using __expand_to_true = true_type; |
| template <class... _Pred> |
| __expand_to_true<__enable_if_t<_Pred::value>...> __and_helper(int); |
| template <class...> |
| false_type __and_helper(...); |
| template <class... _Pred> |
| using _And = decltype(__and_helper<_Pred...>(0)); |
| |
| template <class _Pred> |
| struct _Not : _BoolConstant<!_Pred::value> {}; |
| |
| struct __check_tuple_constructor_fail { |
| static constexpr bool __enable_explicit_default() { return false; } |
| static constexpr bool __enable_implicit_default() { return false; } |
| template <class...> |
| static constexpr bool __enable_explicit() { |
| return false; |
| } |
| template <class...> |
| static constexpr bool __enable_implicit() { |
| return false; |
| } |
| }; |
| |
| template <typename, typename _Tp> |
| struct __select_2nd { |
| typedef _Tp type; |
| }; |
| template <class _Tp, class _Arg> |
| typename __select_2nd<decltype((declval<_Tp>() = declval<_Arg>())), |
| true_type>::type |
| __is_assignable_test(int); |
| template <class, class> |
| false_type __is_assignable_test(...); |
| template <class _Tp, class _Arg, |
| bool = is_void<_Tp>::value || is_void<_Arg>::value> |
| struct __is_assignable_imp |
| : public decltype((__is_assignable_test<_Tp, _Arg>(0))) {}; |
| template <class _Tp, class _Arg> |
| struct __is_assignable_imp<_Tp, _Arg, true> : public false_type {}; |
| template <class _Tp, class _Arg> |
| struct is_assignable : public __is_assignable_imp<_Tp, _Arg> {}; |
| |
| template <class _Tp> |
| struct __libcpp_is_integral : public false_type {}; |
| template <> |
| struct __libcpp_is_integral<bool> : public true_type {}; |
| template <> |
| struct __libcpp_is_integral<char> : public true_type {}; |
| template <> |
| struct __libcpp_is_integral<signed char> : public true_type {}; |
| template <> |
| struct __libcpp_is_integral<unsigned char> : public true_type {}; |
| template <> |
| struct __libcpp_is_integral<wchar_t> : public true_type {}; |
| template <> |
| struct __libcpp_is_integral<short> : public true_type {}; // NOLINT |
| template <> |
| struct __libcpp_is_integral<unsigned short> : public true_type {}; // NOLINT |
| template <> |
| struct __libcpp_is_integral<int> : public true_type {}; |
| template <> |
| struct __libcpp_is_integral<unsigned int> : public true_type {}; |
| template <> |
| struct __libcpp_is_integral<long> : public true_type {}; // NOLINT |
| template <> |
| struct __libcpp_is_integral<unsigned long> : public true_type {}; // NOLINT |
| template <> |
| struct __libcpp_is_integral<long long> : public true_type {}; // NOLINT |
| template <> // NOLINTNEXTLINE |
| struct __libcpp_is_integral<unsigned long long> : public true_type {}; |
| template <class _Tp> |
| struct is_integral |
| : public __libcpp_is_integral<typename remove_cv<_Tp>::type> {}; |
| |
| template <class _Tp> |
| struct __libcpp_is_floating_point : public false_type {}; |
| template <> |
| struct __libcpp_is_floating_point<float> : public true_type {}; |
| template <> |
| struct __libcpp_is_floating_point<double> : public true_type {}; |
| template <> |
| struct __libcpp_is_floating_point<long double> : public true_type {}; |
| template <class _Tp> |
| struct is_floating_point |
| : public __libcpp_is_floating_point<typename remove_cv<_Tp>::type> {}; |
| |
| template <class _Tp> |
| struct is_arithmetic |
| : public integral_constant<bool, is_integral<_Tp>::value || |
| is_floating_point<_Tp>::value> {}; |
| |
| template <class _Tp> |
| struct __libcpp_is_pointer : public false_type {}; |
| template <class _Tp> |
| struct __libcpp_is_pointer<_Tp*> : public true_type {}; |
| template <class _Tp> |
| struct is_pointer : public __libcpp_is_pointer<typename remove_cv<_Tp>::type> { |
| }; |
| |
| template <class _Tp> |
| struct __libcpp_is_member_pointer : public false_type {}; |
| template <class _Tp, class _Up> |
| struct __libcpp_is_member_pointer<_Tp _Up::*> : public true_type {}; |
| template <class _Tp> |
| struct is_member_pointer |
| : public __libcpp_is_member_pointer<typename remove_cv<_Tp>::type> {}; |
| |
| template <class _Tp> |
| struct __libcpp_union : public false_type {}; |
| template <class _Tp> |
| struct is_union : public __libcpp_union<typename remove_cv<_Tp>::type> {}; |
| |
| template <class T> |
| struct is_reference : false_type {}; |
| template <class T> |
| struct is_reference<T&> : true_type {}; |
| template <class T> |
| struct is_reference<T&&> : true_type {}; |
| |
| template <class T> |
| inline constexpr bool is_reference_v = is_reference<T>::value; |
| |
| struct __two { |
| char __lx[2]; |
| }; |
| |
| namespace __is_class_imp { |
| template <class _Tp> |
| char __test(int _Tp::*); |
| template <class _Tp> |
| __two __test(...); |
| } // namespace __is_class_imp |
| template <class _Tp> |
| struct is_class |
| : public integral_constant<bool, |
| sizeof(__is_class_imp::__test<_Tp>(0)) == 1 && |
| !is_union<_Tp>::value> {}; |
| |
| template <class _Tp> |
| struct __is_nullptr_t_impl : public false_type {}; |
| template <> |
| struct __is_nullptr_t_impl<nullptr_t> : public true_type {}; |
| template <class _Tp> |
| struct __is_nullptr_t |
| : public __is_nullptr_t_impl<typename remove_cv<_Tp>::type> {}; |
| template <class _Tp> |
| struct is_null_pointer |
| : public __is_nullptr_t_impl<typename remove_cv<_Tp>::type> {}; |
| |
| template <class _Tp> |
| struct is_enum |
| : public integral_constant< |
| bool, !is_void<_Tp>::value && !is_integral<_Tp>::value && |
| !is_floating_point<_Tp>::value && !is_array<_Tp>::value && |
| !is_pointer<_Tp>::value && !is_reference<_Tp>::value && |
| !is_member_pointer<_Tp>::value && !is_union<_Tp>::value && |
| !is_class<_Tp>::value && !is_function<_Tp>::value> {}; |
| |
| template <class _Tp> |
| struct is_scalar |
| : public integral_constant< |
| bool, is_arithmetic<_Tp>::value || is_member_pointer<_Tp>::value || |
| is_pointer<_Tp>::value || __is_nullptr_t<_Tp>::value || |
| is_enum<_Tp>::value> {}; |
| template <> |
| struct is_scalar<nullptr_t> : public true_type {}; |
| |
| } // namespace std |
| |
| #endif // STD_TYPE_TRAITS_H |
| )"; |
| |
| static constexpr char AbslTypeTraitsHeader[] = R"( |
| #ifndef ABSL_TYPE_TRAITS_H |
| #define ABSL_TYPE_TRAITS_H |
| |
| #include "std_type_traits.h" |
| |
| namespace absl { |
| |
| template <typename... Ts> |
| struct conjunction : std::true_type {}; |
| |
| template <typename T, typename... Ts> |
| struct conjunction<T, Ts...> |
| : std::conditional<T::value, conjunction<Ts...>, T>::type {}; |
| |
| template <typename T> |
| struct conjunction<T> : T {}; |
| |
| template <typename T> |
| struct negation : std::integral_constant<bool, !T::value> {}; |
| |
| template <bool B, typename T = void> |
| using enable_if_t = typename std::enable_if<B, T>::type; |
| |
| } // namespace absl |
| |
| #endif // ABSL_TYPE_TRAITS_H |
| )"; |
| |
| static constexpr char StdStringHeader[] = R"( |
| #ifndef STRING_H |
| #define STRING_H |
| |
| namespace std { |
| |
| struct string { |
| string(const char*); |
| ~string(); |
| bool empty(); |
| }; |
| bool operator!=(const string &LHS, const char *RHS); |
| |
| } // namespace std |
| |
| #endif // STRING_H |
| )"; |
| |
| static constexpr char StdUtilityHeader[] = R"( |
| #ifndef UTILITY_H |
| #define UTILITY_H |
| |
| #include "std_type_traits.h" |
| |
| namespace std { |
| |
| template <typename T> |
| constexpr remove_reference_t<T>&& move(T&& x); |
| |
| template <typename T> |
| void swap(T& a, T& b) noexcept; |
| |
| } // namespace std |
| |
| #endif // UTILITY_H |
| )"; |
| |
| static constexpr char StdInitializerListHeader[] = R"( |
| #ifndef INITIALIZER_LIST_H |
| #define INITIALIZER_LIST_H |
| |
| namespace std { |
| |
| template <typename T> |
| class initializer_list { |
| public: |
| initializer_list() noexcept; |
| }; |
| |
| } // namespace std |
| |
| #endif // INITIALIZER_LIST_H |
| )"; |
| |
| static constexpr char StdOptionalHeader[] = R"( |
| #include "std_initializer_list.h" |
| #include "std_type_traits.h" |
| #include "std_utility.h" |
| |
| namespace std { |
| |
| struct in_place_t {}; |
| constexpr in_place_t in_place; |
| |
| struct nullopt_t { |
| constexpr explicit nullopt_t() {} |
| }; |
| constexpr nullopt_t nullopt; |
| |
| template <class _Tp> |
| struct __optional_destruct_base { |
| constexpr void reset() noexcept; |
| }; |
| |
| template <class _Tp> |
| struct __optional_storage_base : __optional_destruct_base<_Tp> { |
| constexpr bool has_value() const noexcept; |
| }; |
| |
| template <typename _Tp> |
| class optional : private __optional_storage_base<_Tp> { |
| using __base = __optional_storage_base<_Tp>; |
| |
| public: |
| using value_type = _Tp; |
| |
| private: |
| struct _CheckOptionalArgsConstructor { |
| template <class _Up> |
| static constexpr bool __enable_implicit() { |
| return is_constructible_v<_Tp, _Up&&> && is_convertible_v<_Up&&, _Tp>; |
| } |
| |
| template <class _Up> |
| static constexpr bool __enable_explicit() { |
| return is_constructible_v<_Tp, _Up&&> && !is_convertible_v<_Up&&, _Tp>; |
| } |
| }; |
| template <class _Up> |
| using _CheckOptionalArgsCtor = |
| _If<_IsNotSame<__uncvref_t<_Up>, in_place_t>::value && |
| _IsNotSame<__uncvref_t<_Up>, optional>::value, |
| _CheckOptionalArgsConstructor, __check_tuple_constructor_fail>; |
| template <class _QualUp> |
| struct _CheckOptionalLikeConstructor { |
| template <class _Up, class _Opt = optional<_Up>> |
| using __check_constructible_from_opt = |
| _Or<is_constructible<_Tp, _Opt&>, is_constructible<_Tp, _Opt const&>, |
| is_constructible<_Tp, _Opt&&>, is_constructible<_Tp, _Opt const&&>, |
| is_convertible<_Opt&, _Tp>, is_convertible<_Opt const&, _Tp>, |
| is_convertible<_Opt&&, _Tp>, is_convertible<_Opt const&&, _Tp>>; |
| template <class _Up, class _QUp = _QualUp> |
| static constexpr bool __enable_implicit() { |
| return is_convertible<_QUp, _Tp>::value && |
| !__check_constructible_from_opt<_Up>::value; |
| } |
| template <class _Up, class _QUp = _QualUp> |
| static constexpr bool __enable_explicit() { |
| return !is_convertible<_QUp, _Tp>::value && |
| !__check_constructible_from_opt<_Up>::value; |
| } |
| }; |
| |
| template <class _Up, class _QualUp> |
| using _CheckOptionalLikeCtor = |
| _If<_And<_IsNotSame<_Up, _Tp>, is_constructible<_Tp, _QualUp>>::value, |
| _CheckOptionalLikeConstructor<_QualUp>, |
| __check_tuple_constructor_fail>; |
| |
| |
| template <class _Up, class _QualUp> |
| using _CheckOptionalLikeAssign = _If< |
| _And< |
| _IsNotSame<_Up, _Tp>, |
| is_constructible<_Tp, _QualUp>, |
| is_assignable<_Tp&, _QualUp> |
| >::value, |
| _CheckOptionalLikeConstructor<_QualUp>, |
| __check_tuple_constructor_fail |
| >; |
| |
| public: |
| constexpr optional() noexcept {} |
| constexpr optional(const optional&) = default; |
| constexpr optional(optional&&) = default; |
| constexpr optional(nullopt_t) noexcept {} |
| |
| template < |
| class _InPlaceT, class... _Args, |
| class = enable_if_t<_And<_IsSame<_InPlaceT, in_place_t>, |
| is_constructible<value_type, _Args...>>::value>> |
| constexpr explicit optional(_InPlaceT, _Args&&... __args); |
| |
| template <class _Up, class... _Args, |
| class = enable_if_t<is_constructible_v< |
| value_type, initializer_list<_Up>&, _Args...>>> |
| constexpr explicit optional(in_place_t, initializer_list<_Up> __il, |
| _Args&&... __args); |
| |
| template < |
| class _Up = value_type, |
| enable_if_t<_CheckOptionalArgsCtor<_Up>::template __enable_implicit<_Up>(), |
| int> = 0> |
| constexpr optional(_Up&& __v); |
| |
| template < |
| class _Up, |
| enable_if_t<_CheckOptionalArgsCtor<_Up>::template __enable_explicit<_Up>(), |
| int> = 0> |
| constexpr explicit optional(_Up&& __v); |
| |
| template <class _Up, enable_if_t<_CheckOptionalLikeCtor<_Up, _Up const&>:: |
| template __enable_implicit<_Up>(), |
| int> = 0> |
| constexpr optional(const optional<_Up>& __v); |
| |
| template <class _Up, enable_if_t<_CheckOptionalLikeCtor<_Up, _Up const&>:: |
| template __enable_explicit<_Up>(), |
| int> = 0> |
| constexpr explicit optional(const optional<_Up>& __v); |
| |
| template <class _Up, enable_if_t<_CheckOptionalLikeCtor<_Up, _Up&&>:: |
| template __enable_implicit<_Up>(), |
| int> = 0> |
| constexpr optional(optional<_Up>&& __v); |
| |
| template <class _Up, enable_if_t<_CheckOptionalLikeCtor<_Up, _Up&&>:: |
| template __enable_explicit<_Up>(), |
| int> = 0> |
| constexpr explicit optional(optional<_Up>&& __v); |
| |
| constexpr optional& operator=(nullopt_t) noexcept; |
| |
| optional& operator=(const optional&); |
| |
| optional& operator=(optional&&); |
| |
| template <class _Up = value_type, |
| class = enable_if_t<_And<_IsNotSame<__uncvref_t<_Up>, optional>, |
| _Or<_IsNotSame<__uncvref_t<_Up>, value_type>, |
| _Not<is_scalar<value_type>>>, |
| is_constructible<value_type, _Up>, |
| is_assignable<value_type&, _Up>>::value>> |
| constexpr optional& operator=(_Up&& __v); |
| |
| template <class _Up, enable_if_t<_CheckOptionalLikeAssign<_Up, _Up const&>:: |
| template __enable_assign<_Up>(), |
| int> = 0> |
| constexpr optional& operator=(const optional<_Up>& __v); |
| |
| template <class _Up, enable_if_t<_CheckOptionalLikeCtor<_Up, _Up&&>:: |
| template __enable_assign<_Up>(), |
| int> = 0> |
| constexpr optional& operator=(optional<_Up>&& __v); |
| |
| const _Tp& operator*() const&; |
| _Tp& operator*() &; |
| const _Tp&& operator*() const&&; |
| _Tp&& operator*() &&; |
| |
| const _Tp* operator->() const; |
| _Tp* operator->(); |
| |
| const _Tp& value() const&; |
| _Tp& value() &; |
| const _Tp&& value() const&&; |
| _Tp&& value() &&; |
| |
| template <typename U> |
| constexpr _Tp value_or(U&& v) const&; |
| template <typename U> |
| _Tp value_or(U&& v) &&; |
| |
| template <typename... Args> |
| _Tp& emplace(Args&&... args); |
| |
| template <typename U, typename... Args> |
| _Tp& emplace(std::initializer_list<U> ilist, Args&&... args); |
| |
| using __base::reset; |
| |
| constexpr explicit operator bool() const noexcept; |
| using __base::has_value; |
| |
| constexpr void swap(optional& __opt) noexcept; |
| }; |
| |
| template <typename T> |
| constexpr optional<typename std::decay<T>::type> make_optional(T&& v); |
| |
| template <typename T, typename... Args> |
| constexpr optional<T> make_optional(Args&&... args); |
| |
| template <typename T, typename U, typename... Args> |
| constexpr optional<T> make_optional(std::initializer_list<U> il, |
| Args&&... args); |
| |
| template <typename T, typename U> |
| constexpr bool operator==(const optional<T> &lhs, const optional<U> &rhs); |
| template <typename T, typename U> |
| constexpr bool operator!=(const optional<T> &lhs, const optional<U> &rhs); |
| |
| template <typename T> |
| constexpr bool operator==(const optional<T> &opt, nullopt_t); |
| template <typename T> |
| constexpr bool operator==(nullopt_t, const optional<T> &opt); |
| template <typename T> |
| constexpr bool operator!=(const optional<T> &opt, nullopt_t); |
| template <typename T> |
| constexpr bool operator!=(nullopt_t, const optional<T> &opt); |
| |
| template <typename T, typename U> |
| constexpr bool operator==(const optional<T> &opt, const U &value); |
| template <typename T, typename U> |
| constexpr bool operator==(const T &value, const optional<U> &opt); |
| template <typename T, typename U> |
| constexpr bool operator!=(const optional<T> &opt, const U &value); |
| template <typename T, typename U> |
| constexpr bool operator!=(const T &value, const optional<U> &opt); |
| |
| } // namespace std |
| )"; |
| |
| static constexpr char AbslOptionalHeader[] = R"( |
| #include "absl_type_traits.h" |
| #include "std_initializer_list.h" |
| #include "std_type_traits.h" |
| #include "std_utility.h" |
| |
| namespace absl { |
| |
| struct nullopt_t { |
| constexpr explicit nullopt_t() {} |
| }; |
| constexpr nullopt_t nullopt; |
| |
| struct in_place_t {}; |
| constexpr in_place_t in_place; |
| |
| template <typename T> |
| class optional; |
| |
| namespace optional_internal { |
| |
| template <typename T, typename U> |
| struct is_constructible_convertible_from_optional |
| : std::integral_constant< |
| bool, std::is_constructible<T, optional<U>&>::value || |
| std::is_constructible<T, optional<U>&&>::value || |
| std::is_constructible<T, const optional<U>&>::value || |
| std::is_constructible<T, const optional<U>&&>::value || |
| std::is_convertible<optional<U>&, T>::value || |
| std::is_convertible<optional<U>&&, T>::value || |
| std::is_convertible<const optional<U>&, T>::value || |
| std::is_convertible<const optional<U>&&, T>::value> {}; |
| |
| template <typename T, typename U> |
| struct is_constructible_convertible_assignable_from_optional |
| : std::integral_constant< |
| bool, is_constructible_convertible_from_optional<T, U>::value || |
| std::is_assignable<T&, optional<U>&>::value || |
| std::is_assignable<T&, optional<U>&&>::value || |
| std::is_assignable<T&, const optional<U>&>::value || |
| std::is_assignable<T&, const optional<U>&&>::value> {}; |
| |
| } // namespace optional_internal |
| |
| template <typename T> |
| class optional { |
| public: |
| constexpr optional() noexcept; |
| |
| constexpr optional(nullopt_t) noexcept; |
| |
| optional(const optional&) = default; |
| |
| optional(optional&&) = default; |
| |
| template <typename InPlaceT, typename... Args, |
| absl::enable_if_t<absl::conjunction< |
| std::is_same<InPlaceT, in_place_t>, |
| std::is_constructible<T, Args&&...>>::value>* = nullptr> |
| constexpr explicit optional(InPlaceT, Args&&... args); |
| |
| template <typename U, typename... Args, |
| typename = typename std::enable_if<std::is_constructible< |
| T, std::initializer_list<U>&, Args&&...>::value>::type> |
| constexpr explicit optional(in_place_t, std::initializer_list<U> il, |
| Args&&... args); |
| |
| template < |
| typename U = T, |
| typename std::enable_if< |
| absl::conjunction<absl::negation<std::is_same< |
| in_place_t, typename std::decay<U>::type>>, |
| absl::negation<std::is_same< |
| optional<T>, typename std::decay<U>::type>>, |
| std::is_convertible<U&&, T>, |
| std::is_constructible<T, U&&>>::value, |
| bool>::type = false> |
| constexpr optional(U&& v); |
| |
| template < |
| typename U = T, |
| typename std::enable_if< |
| absl::conjunction<absl::negation<std::is_same< |
| in_place_t, typename std::decay<U>::type>>, |
| absl::negation<std::is_same< |
| optional<T>, typename std::decay<U>::type>>, |
| absl::negation<std::is_convertible<U&&, T>>, |
| std::is_constructible<T, U&&>>::value, |
| bool>::type = false> |
| explicit constexpr optional(U&& v); |
| |
| template <typename U, |
| typename std::enable_if< |
| absl::conjunction< |
| absl::negation<std::is_same<T, U>>, |
| std::is_constructible<T, const U&>, |
| absl::negation< |
| optional_internal:: |
| is_constructible_convertible_from_optional<T, U>>, |
| std::is_convertible<const U&, T>>::value, |
| bool>::type = false> |
| optional(const optional<U>& rhs); |
| |
| template <typename U, |
| typename std::enable_if< |
| absl::conjunction< |
| absl::negation<std::is_same<T, U>>, |
| std::is_constructible<T, const U&>, |
| absl::negation< |
| optional_internal:: |
| is_constructible_convertible_from_optional<T, U>>, |
| absl::negation<std::is_convertible<const U&, T>>>::value, |
| bool>::type = false> |
| explicit optional(const optional<U>& rhs); |
| |
| template < |
| typename U, |
| typename std::enable_if< |
| absl::conjunction< |
| absl::negation<std::is_same<T, U>>, std::is_constructible<T, U&&>, |
| absl::negation< |
| optional_internal::is_constructible_convertible_from_optional< |
| T, U>>, |
| std::is_convertible<U&&, T>>::value, |
| bool>::type = false> |
| optional(optional<U>&& rhs); |
| |
| template < |
| typename U, |
| typename std::enable_if< |
| absl::conjunction< |
| absl::negation<std::is_same<T, U>>, std::is_constructible<T, U&&>, |
| absl::negation< |
| optional_internal::is_constructible_convertible_from_optional< |
| T, U>>, |
| absl::negation<std::is_convertible<U&&, T>>>::value, |
| bool>::type = false> |
| explicit optional(optional<U>&& rhs); |
| |
| optional& operator=(nullopt_t) noexcept; |
| |
| optional& operator=(const optional& src); |
| |
| optional& operator=(optional&& src); |
| |
| template < |
| typename U = T, |
| typename = typename std::enable_if<absl::conjunction< |
| absl::negation< |
| std::is_same<optional<T>, typename std::decay<U>::type>>, |
| absl::negation< |
| absl::conjunction<std::is_scalar<T>, |
| std::is_same<T, typename std::decay<U>::type>>>, |
| std::is_constructible<T, U>, std::is_assignable<T&, U>>::value>::type> |
| optional& operator=(U&& v); |
| |
| template < |
| typename U, |
| typename = typename std::enable_if<absl::conjunction< |
| absl::negation<std::is_same<T, U>>, |
| std::is_constructible<T, const U&>, std::is_assignable<T&, const U&>, |
| absl::negation< |
| optional_internal:: |
| is_constructible_convertible_assignable_from_optional< |
| T, U>>>::value>::type> |
| optional& operator=(const optional<U>& rhs); |
| |
| template <typename U, |
| typename = typename std::enable_if<absl::conjunction< |
| absl::negation<std::is_same<T, U>>, std::is_constructible<T, U>, |
| std::is_assignable<T&, U>, |
| absl::negation< |
| optional_internal:: |
| is_constructible_convertible_assignable_from_optional< |
| T, U>>>::value>::type> |
| optional& operator=(optional<U>&& rhs); |
| |
| const T& operator*() const&; |
| T& operator*() &; |
| const T&& operator*() const&&; |
| T&& operator*() &&; |
| |
| const T* operator->() const; |
| T* operator->(); |
| |
| const T& value() const&; |
| T& value() &; |
| const T&& value() const&&; |
| T&& value() &&; |
| |
| template <typename U> |
| constexpr T value_or(U&& v) const&; |
| template <typename U> |
| T value_or(U&& v) &&; |
| |
| template <typename... Args> |
| T& emplace(Args&&... args); |
| |
| template <typename U, typename... Args> |
| T& emplace(std::initializer_list<U> ilist, Args&&... args); |
| |
| void reset() noexcept; |
| |
| constexpr explicit operator bool() const noexcept; |
| constexpr bool has_value() const noexcept; |
| |
| void swap(optional& rhs) noexcept; |
| }; |
| |
| template <typename T> |
| constexpr optional<typename std::decay<T>::type> make_optional(T&& v); |
| |
| template <typename T, typename... Args> |
| constexpr optional<T> make_optional(Args&&... args); |
| |
| template <typename T, typename U, typename... Args> |
| constexpr optional<T> make_optional(std::initializer_list<U> il, |
| Args&&... args); |
| |
| template <typename T, typename U> |
| constexpr bool operator==(const optional<T> &lhs, const optional<U> &rhs); |
| template <typename T, typename U> |
| constexpr bool operator!=(const optional<T> &lhs, const optional<U> &rhs); |
| |
| template <typename T> |
| constexpr bool operator==(const optional<T> &opt, nullopt_t); |
| template <typename T> |
| constexpr bool operator==(nullopt_t, const optional<T> &opt); |
| template <typename T> |
| constexpr bool operator!=(const optional<T> &opt, nullopt_t); |
| template <typename T> |
| constexpr bool operator!=(nullopt_t, const optional<T> &opt); |
| |
| template <typename T, typename U> |
| constexpr bool operator==(const optional<T> &opt, const U &value); |
| template <typename T, typename U> |
| constexpr bool operator==(const T &value, const optional<U> &opt); |
| template <typename T, typename U> |
| constexpr bool operator!=(const optional<T> &opt, const U &value); |
| template <typename T, typename U> |
| constexpr bool operator!=(const T &value, const optional<U> &opt); |
| |
| } // namespace absl |
| )"; |
| |
| static constexpr char BaseOptionalHeader[] = R"( |
| #include "std_initializer_list.h" |
| #include "std_type_traits.h" |
| #include "std_utility.h" |
| |
| namespace base { |
| |
| struct in_place_t {}; |
| constexpr in_place_t in_place; |
| |
| struct nullopt_t { |
| constexpr explicit nullopt_t() {} |
| }; |
| constexpr nullopt_t nullopt; |
| |
| template <typename T> |
| class Optional; |
| |
| namespace internal { |
| |
| template <typename T> |
| using RemoveCvRefT = std::remove_cv_t<std::remove_reference_t<T>>; |
| |
| template <typename T, typename U> |
| struct IsConvertibleFromOptional |
| : std::integral_constant< |
| bool, std::is_constructible<T, Optional<U>&>::value || |
| std::is_constructible<T, const Optional<U>&>::value || |
| std::is_constructible<T, Optional<U>&&>::value || |
| std::is_constructible<T, const Optional<U>&&>::value || |
| std::is_convertible<Optional<U>&, T>::value || |
| std::is_convertible<const Optional<U>&, T>::value || |
| std::is_convertible<Optional<U>&&, T>::value || |
| std::is_convertible<const Optional<U>&&, T>::value> {}; |
| |
| template <typename T, typename U> |
| struct IsAssignableFromOptional |
| : std::integral_constant< |
| bool, IsConvertibleFromOptional<T, U>::value || |
| std::is_assignable<T&, Optional<U>&>::value || |
| std::is_assignable<T&, const Optional<U>&>::value || |
| std::is_assignable<T&, Optional<U>&&>::value || |
| std::is_assignable<T&, const Optional<U>&&>::value> {}; |
| |
| } // namespace internal |
| |
| template <typename T> |
| class Optional { |
| public: |
| using value_type = T; |
| |
| constexpr Optional() = default; |
| constexpr Optional(const Optional& other) noexcept = default; |
| constexpr Optional(Optional&& other) noexcept = default; |
| |
| constexpr Optional(nullopt_t); |
| |
| template <typename U, |
| typename std::enable_if< |
| std::is_constructible<T, const U&>::value && |
| !internal::IsConvertibleFromOptional<T, U>::value && |
| std::is_convertible<const U&, T>::value, |
| bool>::type = false> |
| Optional(const Optional<U>& other) noexcept; |
| |
| template <typename U, |
| typename std::enable_if< |
| std::is_constructible<T, const U&>::value && |
| !internal::IsConvertibleFromOptional<T, U>::value && |
| !std::is_convertible<const U&, T>::value, |
| bool>::type = false> |
| explicit Optional(const Optional<U>& other) noexcept; |
| |
| template <typename U, |
| typename std::enable_if< |
| std::is_constructible<T, U&&>::value && |
| !internal::IsConvertibleFromOptional<T, U>::value && |
| std::is_convertible<U&&, T>::value, |
| bool>::type = false> |
| Optional(Optional<U>&& other) noexcept; |
| |
| template <typename U, |
| typename std::enable_if< |
| std::is_constructible<T, U&&>::value && |
| !internal::IsConvertibleFromOptional<T, U>::value && |
| !std::is_convertible<U&&, T>::value, |
| bool>::type = false> |
| explicit Optional(Optional<U>&& other) noexcept; |
| |
| template <class... Args> |
| constexpr explicit Optional(in_place_t, Args&&... args); |
| |
| template <class U, class... Args, |
| class = typename std::enable_if<std::is_constructible< |
| value_type, std::initializer_list<U>&, Args...>::value>::type> |
| constexpr explicit Optional(in_place_t, std::initializer_list<U> il, |
| Args&&... args); |
| |
| template < |
| typename U = value_type, |
| typename std::enable_if< |
| std::is_constructible<T, U&&>::value && |
| !std::is_same<internal::RemoveCvRefT<U>, in_place_t>::value && |
| !std::is_same<internal::RemoveCvRefT<U>, Optional<T>>::value && |
| std::is_convertible<U&&, T>::value, |
| bool>::type = false> |
| constexpr Optional(U&& value); |
| |
| template < |
| typename U = value_type, |
| typename std::enable_if< |
| std::is_constructible<T, U&&>::value && |
| !std::is_same<internal::RemoveCvRefT<U>, in_place_t>::value && |
| !std::is_same<internal::RemoveCvRefT<U>, Optional<T>>::value && |
| !std::is_convertible<U&&, T>::value, |
| bool>::type = false> |
| constexpr explicit Optional(U&& value); |
| |
| Optional& operator=(const Optional& other) noexcept; |
| |
| Optional& operator=(Optional&& other) noexcept; |
| |
| Optional& operator=(nullopt_t); |
| |
| template <typename U> |
| typename std::enable_if< |
| !std::is_same<internal::RemoveCvRefT<U>, Optional<T>>::value && |
| std::is_constructible<T, U>::value && |
| std::is_assignable<T&, U>::value && |
| (!std::is_scalar<T>::value || |
| !std::is_same<typename std::decay<U>::type, T>::value), |
| Optional&>::type |
| operator=(U&& value) noexcept; |
| |
| template <typename U> |
| typename std::enable_if<!internal::IsAssignableFromOptional<T, U>::value && |
| std::is_constructible<T, const U&>::value && |
| std::is_assignable<T&, const U&>::value, |
| Optional&>::type |
| operator=(const Optional<U>& other) noexcept; |
| |
| template <typename U> |
| typename std::enable_if<!internal::IsAssignableFromOptional<T, U>::value && |
| std::is_constructible<T, U>::value && |
| std::is_assignable<T&, U>::value, |
| Optional&>::type |
| operator=(Optional<U>&& other) noexcept; |
| |
| const T& operator*() const&; |
| T& operator*() &; |
| const T&& operator*() const&&; |
| T&& operator*() &&; |
| |
| const T* operator->() const; |
| T* operator->(); |
| |
| const T& value() const&; |
| T& value() &; |
| const T&& value() const&&; |
| T&& value() &&; |
| |
| template <typename U> |
| constexpr T value_or(U&& v) const&; |
| template <typename U> |
| T value_or(U&& v) &&; |
| |
| template <typename... Args> |
| T& emplace(Args&&... args); |
| |
| template <typename U, typename... Args> |
| T& emplace(std::initializer_list<U> ilist, Args&&... args); |
| |
| void reset() noexcept; |
| |
| constexpr explicit operator bool() const noexcept; |
| constexpr bool has_value() const noexcept; |
| |
| void swap(Optional& other); |
| }; |
| |
| template <typename T> |
| constexpr Optional<typename std::decay<T>::type> make_optional(T&& v); |
| |
| template <typename T, typename... Args> |
| constexpr Optional<T> make_optional(Args&&... args); |
| |
| template <typename T, typename U, typename... Args> |
| constexpr Optional<T> make_optional(std::initializer_list<U> il, |
| Args&&... args); |
| |
| template <typename T, typename U> |
| constexpr bool operator==(const Optional<T> &lhs, const Optional<U> &rhs); |
| template <typename T, typename U> |
| constexpr bool operator!=(const Optional<T> &lhs, const Optional<U> &rhs); |
| |
| template <typename T> |
| constexpr bool operator==(const Optional<T> &opt, nullopt_t); |
| template <typename T> |
| constexpr bool operator==(nullopt_t, const Optional<T> &opt); |
| template <typename T> |
| constexpr bool operator!=(const Optional<T> &opt, nullopt_t); |
| template <typename T> |
| constexpr bool operator!=(nullopt_t, const Optional<T> &opt); |
| |
| template <typename T, typename U> |
| constexpr bool operator==(const Optional<T> &opt, const U &value); |
| template <typename T, typename U> |
| constexpr bool operator==(const T &value, const Optional<U> &opt); |
| template <typename T, typename U> |
| constexpr bool operator!=(const Optional<T> &opt, const U &value); |
| template <typename T, typename U> |
| constexpr bool operator!=(const T &value, const Optional<U> &opt); |
| |
| } // namespace base |
| )"; |
| |
| /// Replaces all occurrences of `Pattern` in `S` with `Replacement`. |
| static void ReplaceAllOccurrences(std::string &S, const std::string &Pattern, |
| const std::string &Replacement) { |
| size_t Pos = 0; |
| while (true) { |
| Pos = S.find(Pattern, Pos); |
| if (Pos == std::string::npos) |
| break; |
| S.replace(Pos, Pattern.size(), Replacement); |
| } |
| } |
| |
| struct OptionalTypeIdentifier { |
| std::string NamespaceName; |
| std::string TypeName; |
| }; |
| |
| static raw_ostream &operator<<(raw_ostream &OS, |
| const OptionalTypeIdentifier &TypeId) { |
| OS << TypeId.NamespaceName << "::" << TypeId.TypeName; |
| return OS; |
| } |
| |
| class UncheckedOptionalAccessTest |
| : public ::testing::TestWithParam<OptionalTypeIdentifier> { |
| protected: |
| void ExpectDiagnosticsFor(std::string SourceCode) { |
| ExpectDiagnosticsFor(SourceCode, ast_matchers::hasName("target")); |
| } |
| |
| template <typename FuncDeclMatcher> |
| void ExpectDiagnosticsFor(std::string SourceCode, |
| FuncDeclMatcher FuncMatcher) { |
| ReplaceAllOccurrences(SourceCode, "$ns", GetParam().NamespaceName); |
| ReplaceAllOccurrences(SourceCode, "$optional", GetParam().TypeName); |
| |
| std::vector<std::pair<std::string, std::string>> Headers; |
| Headers.emplace_back("cstddef.h", CSDtdDefHeader); |
| Headers.emplace_back("std_initializer_list.h", StdInitializerListHeader); |
| Headers.emplace_back("std_string.h", StdStringHeader); |
| Headers.emplace_back("std_type_traits.h", StdTypeTraitsHeader); |
| Headers.emplace_back("std_utility.h", StdUtilityHeader); |
| Headers.emplace_back("std_optional.h", StdOptionalHeader); |
| Headers.emplace_back("absl_type_traits.h", AbslTypeTraitsHeader); |
| Headers.emplace_back("absl_optional.h", AbslOptionalHeader); |
| Headers.emplace_back("base_optional.h", BaseOptionalHeader); |
| Headers.emplace_back("unchecked_optional_access_test.h", R"( |
| #include "absl_optional.h" |
| #include "base_optional.h" |
| #include "std_initializer_list.h" |
| #include "std_optional.h" |
| #include "std_string.h" |
| #include "std_utility.h" |
| |
| template <typename T> |
| T Make(); |
| )"); |
| UncheckedOptionalAccessModelOptions Options{ |
| /*IgnoreSmartPointerDereference=*/true}; |
| std::vector<SourceLocation> Diagnostics; |
| llvm::Error Error = checkDataflow<UncheckedOptionalAccessModel>( |
| AnalysisInputs<UncheckedOptionalAccessModel>( |
| SourceCode, std::move(FuncMatcher), |
| [](ASTContext &Ctx, Environment &) { |
| return UncheckedOptionalAccessModel(Ctx); |
| }) |
| .withPostVisitCFG( |
| [&Diagnostics, |
| Diagnoser = UncheckedOptionalAccessDiagnoser(Options)]( |
| ASTContext &Ctx, const CFGElement &Elt, |
| const TransferStateForDiagnostics<NoopLattice> |
| &State) mutable { |
| auto EltDiagnostics = |
| Diagnoser.diagnose(Ctx, &Elt, State.Env); |
| llvm::move(EltDiagnostics, std::back_inserter(Diagnostics)); |
| }) |
| .withASTBuildArgs( |
| {"-fsyntax-only", "-std=c++17", "-Wno-undefined-inline"}) |
| .withASTBuildVirtualMappedFiles( |
| tooling::FileContentMappings(Headers.begin(), Headers.end())), |
| /*VerifyResults=*/[&Diagnostics]( |
| const llvm::DenseMap<unsigned, std::string> |
| &Annotations, |
| const AnalysisOutputs &AO) { |
| llvm::DenseSet<unsigned> AnnotationLines; |
| for (const auto &[Line, _] : Annotations) { |
| AnnotationLines.insert(Line); |
| } |
| auto &SrcMgr = AO.ASTCtx.getSourceManager(); |
| llvm::DenseSet<unsigned> DiagnosticLines; |
| for (SourceLocation &Loc : Diagnostics) { |
| unsigned Line = SrcMgr.getPresumedLineNumber(Loc); |
| DiagnosticLines.insert(Line); |
| if (!AnnotationLines.contains(Line)) { |
| IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts( |
| new DiagnosticOptions()); |
| TextDiagnostic TD(llvm::errs(), AO.ASTCtx.getLangOpts(), |
| DiagOpts.get()); |
| TD.emitDiagnostic( |
| FullSourceLoc(Loc, SrcMgr), DiagnosticsEngine::Error, |
| "unexpected diagnostic", std::nullopt, std::nullopt); |
| } |
| } |
| |
| EXPECT_THAT(DiagnosticLines, ContainerEq(AnnotationLines)); |
| }); |
| if (Error) |
| FAIL() << llvm::toString(std::move(Error)); |
| } |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| UncheckedOptionalUseTestInst, UncheckedOptionalAccessTest, |
| ::testing::Values(OptionalTypeIdentifier{"std", "optional"}, |
| OptionalTypeIdentifier{"absl", "optional"}, |
| OptionalTypeIdentifier{"base", "Optional"}), |
| [](const ::testing::TestParamInfo<OptionalTypeIdentifier> &Info) { |
| return Info.param.NamespaceName; |
| }); |
| |
| // Verifies that similarly-named types are ignored. |
| TEST_P(UncheckedOptionalAccessTest, NonTrackedOptionalType) { |
| ExpectDiagnosticsFor( |
| R"( |
| namespace other { |
| namespace $ns { |
| template <typename T> |
| struct $optional { |
| T value(); |
| }; |
| } |
| |
| void target($ns::$optional<int> opt) { |
| opt.value(); |
| } |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, EmptyFunctionBody) { |
| ExpectDiagnosticsFor(R"( |
| void target() { |
| (void)0; |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, UnwrapUsingValueNoCheck) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target($ns::$optional<int> opt) { |
| opt.value(); // [[unsafe]] |
| } |
| )"); |
| |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target($ns::$optional<int> opt) { |
| std::move(opt).value(); // [[unsafe]] |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, UnwrapUsingOperatorStarNoCheck) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target($ns::$optional<int> opt) { |
| *opt; // [[unsafe]] |
| } |
| )"); |
| |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target($ns::$optional<int> opt) { |
| *std::move(opt); // [[unsafe]] |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, UnwrapUsingOperatorArrowNoCheck) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct Foo { |
| void foo(); |
| }; |
| |
| void target($ns::$optional<Foo> opt) { |
| opt->foo(); // [[unsafe]] |
| } |
| )"); |
| |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct Foo { |
| void foo(); |
| }; |
| |
| void target($ns::$optional<Foo> opt) { |
| std::move(opt)->foo(); // [[unsafe]] |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, HasValueCheck) { |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target($ns::$optional<int> opt) { |
| if (opt.has_value()) { |
| opt.value(); |
| } |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, OperatorBoolCheck) { |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target($ns::$optional<int> opt) { |
| if (opt) { |
| opt.value(); |
| } |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, UnwrapFunctionCallResultNoCheck) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| Make<$ns::$optional<int>>().value(); // [[unsafe]] |
| (void)0; |
| } |
| )"); |
| |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target($ns::$optional<int> opt) { |
| std::move(opt).value(); // [[unsafe]] |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, DefaultConstructor) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt; |
| opt.value(); // [[unsafe]] |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, NulloptConstructor) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt($ns::nullopt); |
| opt.value(); // [[unsafe]] |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, NulloptConstructorWithSugaredType) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| template <typename T> |
| using wrapper = T; |
| |
| template <typename T> |
| wrapper<T> wrap(T); |
| |
| void target() { |
| $ns::$optional<int> opt(wrap($ns::nullopt)); |
| opt.value(); // [[unsafe]] |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, InPlaceConstructor) { |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt($ns::in_place, 3); |
| opt.value(); |
| } |
| )"); |
| |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct Foo {}; |
| |
| void target() { |
| $ns::$optional<Foo> opt($ns::in_place); |
| opt.value(); |
| } |
| )"); |
| |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct Foo { |
| explicit Foo(int, bool); |
| }; |
| |
| void target() { |
| $ns::$optional<Foo> opt($ns::in_place, 3, false); |
| opt.value(); |
| } |
| )"); |
| |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct Foo { |
| explicit Foo(std::initializer_list<int>); |
| }; |
| |
| void target() { |
| $ns::$optional<Foo> opt($ns::in_place, {3}); |
| opt.value(); |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, ValueConstructor) { |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt(21); |
| opt.value(); |
| } |
| )"); |
| |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt = $ns::$optional<int>(21); |
| opt.value(); |
| } |
| )"); |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<$ns::$optional<int>> opt(Make<$ns::$optional<int>>()); |
| opt.value(); |
| } |
| )"); |
| |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct MyString { |
| MyString(const char*); |
| }; |
| |
| void target() { |
| $ns::$optional<MyString> opt("foo"); |
| opt.value(); |
| } |
| )"); |
| |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct Foo {}; |
| |
| struct Bar { |
| Bar(const Foo&); |
| }; |
| |
| void target() { |
| $ns::$optional<Bar> opt(Make<Foo>()); |
| opt.value(); |
| } |
| )"); |
| |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct Foo { |
| explicit Foo(int); |
| }; |
| |
| void target() { |
| $ns::$optional<Foo> opt(3); |
| opt.value(); |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, ConvertibleOptionalConstructor) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct Foo {}; |
| |
| struct Bar { |
| Bar(const Foo&); |
| }; |
| |
| void target() { |
| $ns::$optional<Bar> opt(Make<$ns::$optional<Foo>>()); |
| opt.value(); // [[unsafe]] |
| } |
| )"); |
| |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct Foo {}; |
| |
| struct Bar { |
| explicit Bar(const Foo&); |
| }; |
| |
| void target() { |
| $ns::$optional<Bar> opt(Make<$ns::$optional<Foo>>()); |
| opt.value(); // [[unsafe]] |
| } |
| )"); |
| |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct Foo {}; |
| |
| struct Bar { |
| Bar(const Foo&); |
| }; |
| |
| void target() { |
| $ns::$optional<Foo> opt1 = $ns::nullopt; |
| $ns::$optional<Bar> opt2(opt1); |
| opt2.value(); // [[unsafe]] |
| } |
| )"); |
| |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct Foo {}; |
| |
| struct Bar { |
| Bar(const Foo&); |
| }; |
| |
| void target() { |
| $ns::$optional<Foo> opt1(Make<Foo>()); |
| $ns::$optional<Bar> opt2(opt1); |
| opt2.value(); |
| } |
| )"); |
| |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct Foo {}; |
| |
| struct Bar { |
| explicit Bar(const Foo&); |
| }; |
| |
| void target() { |
| $ns::$optional<Foo> opt1(Make<Foo>()); |
| $ns::$optional<Bar> opt2(opt1); |
| opt2.value(); |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, MakeOptional) { |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt = $ns::make_optional(0); |
| opt.value(); |
| } |
| )"); |
| |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct Foo { |
| Foo(int, int); |
| }; |
| |
| void target() { |
| $ns::$optional<Foo> opt = $ns::make_optional<Foo>(21, 22); |
| opt.value(); |
| } |
| )"); |
| |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct Foo { |
| constexpr Foo(std::initializer_list<char>); |
| }; |
| |
| void target() { |
| char a = 'a'; |
| $ns::$optional<Foo> opt = $ns::make_optional<Foo>({a}); |
| opt.value(); |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, ValueOr) { |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt; |
| opt.value_or(0); |
| (void)0; |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, ValueOrComparisonPointers) { |
| ExpectDiagnosticsFor( |
| R"code( |
| #include "unchecked_optional_access_test.h" |
| |
| void target($ns::$optional<int*> opt) { |
| if (opt.value_or(nullptr) != nullptr) { |
| opt.value(); |
| } else { |
| opt.value(); // [[unsafe]] |
| } |
| } |
| )code"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, ValueOrComparisonIntegers) { |
| ExpectDiagnosticsFor( |
| R"code( |
| #include "unchecked_optional_access_test.h" |
| |
| void target($ns::$optional<int> opt) { |
| if (opt.value_or(0) != 0) { |
| opt.value(); |
| } else { |
| opt.value(); // [[unsafe]] |
| } |
| } |
| )code"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, ValueOrComparisonStrings) { |
| ExpectDiagnosticsFor( |
| R"code( |
| #include "unchecked_optional_access_test.h" |
| |
| void target($ns::$optional<std::string> opt) { |
| if (!opt.value_or("").empty()) { |
| opt.value(); |
| } else { |
| opt.value(); // [[unsafe]] |
| } |
| } |
| )code"); |
| |
| ExpectDiagnosticsFor( |
| R"code( |
| #include "unchecked_optional_access_test.h" |
| |
| void target($ns::$optional<std::string> opt) { |
| if (opt.value_or("") != "") { |
| opt.value(); |
| } else { |
| opt.value(); // [[unsafe]] |
| } |
| } |
| )code"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, ValueOrComparisonPointerToOptional) { |
| // FIXME: make `opt` a parameter directly, once we ensure that all `optional` |
| // values have a `has_value` property. |
| ExpectDiagnosticsFor( |
| R"code( |
| #include "unchecked_optional_access_test.h" |
| |
| void target($ns::$optional<int> p) { |
| $ns::$optional<int> *opt = &p; |
| if (opt->value_or(0) != 0) { |
| opt->value(); |
| } else { |
| opt->value(); // [[unsafe]] |
| } |
| } |
| )code"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, Emplace) { |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt; |
| opt.emplace(0); |
| opt.value(); |
| } |
| )"); |
| |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target($ns::$optional<int> *opt) { |
| opt->emplace(0); |
| opt->value(); |
| } |
| )"); |
| |
| // FIXME: Add tests that call `emplace` in conditional branches: |
| // ExpectDiagnosticsFor( |
| // R"( |
| // #include "unchecked_optional_access_test.h" |
| // |
| // void target($ns::$optional<int> opt, bool b) { |
| // if (b) { |
| // opt.emplace(0); |
| // } |
| // if (b) { |
| // opt.value(); |
| // } else { |
| // opt.value(); // [[unsafe]] |
| // } |
| // } |
| // )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, Reset) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt = $ns::make_optional(0); |
| opt.reset(); |
| opt.value(); // [[unsafe]] |
| } |
| )"); |
| |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target($ns::$optional<int> &opt) { |
| if (opt.has_value()) { |
| opt.reset(); |
| opt.value(); // [[unsafe]] |
| } |
| } |
| )"); |
| |
| // FIXME: Add tests that call `reset` in conditional branches: |
| // ExpectDiagnosticsFor( |
| // R"( |
| // #include "unchecked_optional_access_test.h" |
| // |
| // void target(bool b) { |
| // $ns::$optional<int> opt = $ns::make_optional(0); |
| // if (b) { |
| // opt.reset(); |
| // } |
| // if (b) { |
| // opt.value(); // [[unsafe]] |
| // } else { |
| // opt.value(); |
| // } |
| // } |
| // )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, ValueAssignment) { |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct Foo {}; |
| |
| void target() { |
| $ns::$optional<Foo> opt; |
| opt = Foo(); |
| opt.value(); |
| } |
| )"); |
| |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct Foo {}; |
| |
| void target() { |
| $ns::$optional<Foo> opt; |
| (opt = Foo()).value(); |
| (void)0; |
| } |
| )"); |
| |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct MyString { |
| MyString(const char*); |
| }; |
| |
| void target() { |
| $ns::$optional<MyString> opt; |
| opt = "foo"; |
| opt.value(); |
| } |
| )"); |
| |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct MyString { |
| MyString(const char*); |
| }; |
| |
| void target() { |
| $ns::$optional<MyString> opt; |
| (opt = "foo").value(); |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, OptionalConversionAssignment) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct Foo {}; |
| |
| struct Bar { |
| Bar(const Foo&); |
| }; |
| |
| void target() { |
| $ns::$optional<Foo> opt1 = Foo(); |
| $ns::$optional<Bar> opt2; |
| opt2 = opt1; |
| opt2.value(); |
| } |
| )"); |
| |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct Foo {}; |
| |
| struct Bar { |
| Bar(const Foo&); |
| }; |
| |
| void target() { |
| $ns::$optional<Foo> opt1; |
| $ns::$optional<Bar> opt2; |
| if (opt2.has_value()) { |
| opt2 = opt1; |
| opt2.value(); // [[unsafe]] |
| } |
| } |
| )"); |
| |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct Foo {}; |
| |
| struct Bar { |
| Bar(const Foo&); |
| }; |
| |
| void target() { |
| $ns::$optional<Foo> opt1 = Foo(); |
| $ns::$optional<Bar> opt2; |
| (opt2 = opt1).value(); |
| (void)0; |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, NulloptAssignment) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt = 3; |
| opt = $ns::nullopt; |
| opt.value(); // [[unsafe]] |
| } |
| )"); |
| |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt = 3; |
| (opt = $ns::nullopt).value(); // [[unsafe]] |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, OptionalSwap) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt1 = $ns::nullopt; |
| $ns::$optional<int> opt2 = 3; |
| |
| opt1.swap(opt2); |
| |
| opt1.value(); |
| |
| opt2.value(); // [[unsafe]] |
| } |
| )"); |
| |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt1 = $ns::nullopt; |
| $ns::$optional<int> opt2 = 3; |
| |
| opt2.swap(opt1); |
| |
| opt1.value(); |
| |
| opt2.value(); // [[unsafe]] |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, StdSwap) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt1 = $ns::nullopt; |
| $ns::$optional<int> opt2 = 3; |
| |
| std::swap(opt1, opt2); |
| |
| opt1.value(); |
| |
| opt2.value(); // [[unsafe]] |
| } |
| )"); |
| |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt1 = $ns::nullopt; |
| $ns::$optional<int> opt2 = 3; |
| |
| std::swap(opt2, opt1); |
| |
| opt1.value(); |
| |
| opt2.value(); // [[unsafe]] |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, SwapUnmodeledLocLeft) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct L { $ns::$optional<int> hd; L* tl; }; |
| |
| void target() { |
| $ns::$optional<int> foo = 3; |
| L bar; |
| |
| // Any `tl` beyond the first is not modeled. |
| bar.tl->tl->hd.swap(foo); |
| |
| bar.tl->tl->hd.value(); // [[unsafe]] |
| foo.value(); // [[unsafe]] |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, SwapUnmodeledLocRight) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct L { $ns::$optional<int> hd; L* tl; }; |
| |
| void target() { |
| $ns::$optional<int> foo = 3; |
| L bar; |
| |
| // Any `tl` beyond the first is not modeled. |
| foo.swap(bar.tl->tl->hd); |
| |
| bar.tl->tl->hd.value(); // [[unsafe]] |
| foo.value(); // [[unsafe]] |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, SwapUnmodeledValueLeftSet) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct S { int x; }; |
| struct A { $ns::$optional<S> late; }; |
| struct B { A f3; }; |
| struct C { B f2; }; |
| struct D { C f1; }; |
| |
| void target() { |
| $ns::$optional<S> foo = S{3}; |
| D bar; |
| |
| bar.f1.f2.f3.late.swap(foo); |
| |
| bar.f1.f2.f3.late.value(); |
| foo.value(); // [[unsafe]] |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, SwapUnmodeledValueLeftUnset) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct S { int x; }; |
| struct A { $ns::$optional<S> late; }; |
| struct B { A f3; }; |
| struct C { B f2; }; |
| struct D { C f1; }; |
| |
| void target() { |
| $ns::$optional<S> foo; |
| D bar; |
| |
| bar.f1.f2.f3.late.swap(foo); |
| |
| bar.f1.f2.f3.late.value(); // [[unsafe]] |
| foo.value(); // [[unsafe]] |
| } |
| )"); |
| } |
| |
| // fixme: use recursion instead of depth. |
| TEST_P(UncheckedOptionalAccessTest, SwapUnmodeledValueRightSet) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct S { int x; }; |
| struct A { $ns::$optional<S> late; }; |
| struct B { A f3; }; |
| struct C { B f2; }; |
| struct D { C f1; }; |
| |
| void target() { |
| $ns::$optional<S> foo = S{3}; |
| D bar; |
| |
| foo.swap(bar.f1.f2.f3.late); |
| |
| bar.f1.f2.f3.late.value(); |
| foo.value(); // [[unsafe]] |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, SwapUnmodeledValueRightUnset) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct S { int x; }; |
| struct A { $ns::$optional<S> late; }; |
| struct B { A f3; }; |
| struct C { B f2; }; |
| struct D { C f1; }; |
| |
| void target() { |
| $ns::$optional<S> foo; |
| D bar; |
| |
| foo.swap(bar.f1.f2.f3.late); |
| |
| bar.f1.f2.f3.late.value(); // [[unsafe]] |
| foo.value(); // [[unsafe]] |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, UniquePtrToOptional) { |
| // We suppress diagnostics for optionals in smart pointers (other than |
| // `optional` itself). |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| template <typename T> |
| struct smart_ptr { |
| T& operator*() &; |
| T* operator->(); |
| }; |
| |
| void target() { |
| smart_ptr<$ns::$optional<bool>> foo; |
| foo->value(); |
| (*foo).value(); |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, UniquePtrToStructWithOptionalField) { |
| // We suppress diagnostics for optional fields reachable from smart pointers |
| // (other than `optional` itself) through (exactly) one member access. |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| template <typename T> |
| struct smart_ptr { |
| T& operator*() &; |
| T* operator->(); |
| }; |
| |
| struct Foo { |
| $ns::$optional<int> opt; |
| }; |
| |
| void target() { |
| smart_ptr<Foo> foo; |
| *foo->opt; |
| *(*foo).opt; |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, CallReturningOptional) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| $ns::$optional<int> MakeOpt(); |
| |
| void target() { |
| $ns::$optional<int> opt = 0; |
| opt = MakeOpt(); |
| opt.value(); // [[unsafe]] |
| } |
| )"); |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| const $ns::$optional<int>& MakeOpt(); |
| |
| void target() { |
| $ns::$optional<int> opt = 0; |
| opt = MakeOpt(); |
| opt.value(); // [[unsafe]] |
| } |
| )"); |
| |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| using IntOpt = $ns::$optional<int>; |
| IntOpt MakeOpt(); |
| |
| void target() { |
| IntOpt opt = 0; |
| opt = MakeOpt(); |
| opt.value(); // [[unsafe]] |
| } |
| )"); |
| |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| using IntOpt = $ns::$optional<int>; |
| const IntOpt& MakeOpt(); |
| |
| void target() { |
| IntOpt opt = 0; |
| opt = MakeOpt(); |
| opt.value(); // [[unsafe]] |
| } |
| )"); |
| } |
| |
| |
| TEST_P(UncheckedOptionalAccessTest, EqualityCheckLeftSet) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt1 = 3; |
| $ns::$optional<int> opt2 = Make<$ns::$optional<int>>(); |
| |
| if (opt1 == opt2) { |
| opt2.value(); |
| } else { |
| opt2.value(); // [[unsafe]] |
| } |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, EqualityCheckRightSet) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt1 = 3; |
| $ns::$optional<int> opt2 = Make<$ns::$optional<int>>(); |
| |
| if (opt2 == opt1) { |
| opt2.value(); |
| } else { |
| opt2.value(); // [[unsafe]] |
| } |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, EqualityCheckVerifySetAfterEq) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt1 = Make<$ns::$optional<int>>(); |
| $ns::$optional<int> opt2 = Make<$ns::$optional<int>>(); |
| |
| if (opt1 == opt2) { |
| if (opt1.has_value()) |
| opt2.value(); |
| if (opt2.has_value()) |
| opt1.value(); |
| } |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, EqualityCheckLeftUnset) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt1 = $ns::nullopt; |
| $ns::$optional<int> opt2 = Make<$ns::$optional<int>>(); |
| |
| if (opt1 == opt2) { |
| opt2.value(); // [[unsafe]] |
| } else { |
| opt2.value(); |
| } |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, EqualityCheckRightUnset) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt1 = $ns::nullopt; |
| $ns::$optional<int> opt2 = Make<$ns::$optional<int>>(); |
| |
| if (opt2 == opt1) { |
| opt2.value(); // [[unsafe]] |
| } else { |
| opt2.value(); |
| } |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, EqualityCheckRightNullopt) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt = Make<$ns::$optional<int>>(); |
| |
| if (opt == $ns::nullopt) { |
| opt.value(); // [[unsafe]] |
| } else { |
| opt.value(); |
| } |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, EqualityCheckLeftNullopt) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt = Make<$ns::$optional<int>>(); |
| |
| if ($ns::nullopt == opt) { |
| opt.value(); // [[unsafe]] |
| } else { |
| opt.value(); |
| } |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, EqualityCheckRightValue) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt = Make<$ns::$optional<int>>(); |
| |
| if (opt == 3) { |
| opt.value(); |
| } else { |
| opt.value(); // [[unsafe]] |
| } |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, EqualityCheckLeftValue) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt = Make<$ns::$optional<int>>(); |
| |
| if (3 == opt) { |
| opt.value(); |
| } else { |
| opt.value(); // [[unsafe]] |
| } |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, InequalityCheckLeftSet) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt1 = 3; |
| $ns::$optional<int> opt2 = Make<$ns::$optional<int>>(); |
| |
| if (opt1 != opt2) { |
| opt2.value(); // [[unsafe]] |
| } else { |
| opt2.value(); |
| } |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, InequalityCheckRightSet) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt1 = 3; |
| $ns::$optional<int> opt2 = Make<$ns::$optional<int>>(); |
| |
| if (opt2 != opt1) { |
| opt2.value(); // [[unsafe]] |
| } else { |
| opt2.value(); |
| } |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, InequalityCheckVerifySetAfterEq) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt1 = Make<$ns::$optional<int>>(); |
| $ns::$optional<int> opt2 = Make<$ns::$optional<int>>(); |
| |
| if (opt1 != opt2) { |
| if (opt1.has_value()) |
| opt2.value(); // [[unsafe]] |
| if (opt2.has_value()) |
| opt1.value(); // [[unsafe]] |
| } |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, InequalityCheckLeftUnset) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt1 = $ns::nullopt; |
| $ns::$optional<int> opt2 = Make<$ns::$optional<int>>(); |
| |
| if (opt1 != opt2) { |
| opt2.value(); |
| } else { |
| opt2.value(); // [[unsafe]] |
| } |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, InequalityCheckRightUnset) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt1 = $ns::nullopt; |
| $ns::$optional<int> opt2 = Make<$ns::$optional<int>>(); |
| |
| if (opt2 != opt1) { |
| opt2.value(); |
| } else { |
| opt2.value(); // [[unsafe]] |
| } |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, InequalityCheckRightNullopt) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt = Make<$ns::$optional<int>>(); |
| |
| if (opt != $ns::nullopt) { |
| opt.value(); |
| } else { |
| opt.value(); // [[unsafe]] |
| } |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, InequalityCheckLeftNullopt) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt = Make<$ns::$optional<int>>(); |
| |
| if ($ns::nullopt != opt) { |
| opt.value(); |
| } else { |
| opt.value(); // [[unsafe]] |
| } |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, InequalityCheckRightValue) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt = Make<$ns::$optional<int>>(); |
| |
| if (opt != 3) { |
| opt.value(); // [[unsafe]] |
| } else { |
| opt.value(); |
| } |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, InequalityCheckLeftValue) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt = Make<$ns::$optional<int>>(); |
| |
| if (3 != opt) { |
| opt.value(); // [[unsafe]] |
| } else { |
| opt.value(); |
| } |
| } |
| )"); |
| } |
| |
| // Verifies that the model sees through aliases. |
| TEST_P(UncheckedOptionalAccessTest, WithAlias) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| template <typename T> |
| using MyOptional = $ns::$optional<T>; |
| |
| void target(MyOptional<int> opt) { |
| opt.value(); // [[unsafe]] |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, OptionalValueOptional) { |
| // Basic test that nested values are populated. We nest an optional because |
| // its easy to use in a test, but the type of the nested value shouldn't |
| // matter. |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| using Foo = $ns::$optional<std::string>; |
| |
| void target($ns::$optional<Foo> foo) { |
| if (foo && *foo) { |
| foo->value(); |
| } |
| } |
| )"); |
| |
| // Mutation is supported for nested values. |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| using Foo = $ns::$optional<std::string>; |
| |
| void target($ns::$optional<Foo> foo) { |
| if (foo && *foo) { |
| foo->reset(); |
| foo->value(); // [[unsafe]] |
| } |
| } |
| )"); |
| } |
| |
| // Tests that structs can be nested. We use an optional field because its easy |
| // to use in a test, but the type of the field shouldn't matter. |
| TEST_P(UncheckedOptionalAccessTest, OptionalValueStruct) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct Foo { |
| $ns::$optional<std::string> opt; |
| }; |
| |
| void target($ns::$optional<Foo> foo) { |
| if (foo && foo->opt) { |
| foo->opt.value(); |
| } |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, OptionalValueInitialization) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| using Foo = $ns::$optional<std::string>; |
| |
| void target($ns::$optional<Foo> foo, bool b) { |
| if (!foo.has_value()) return; |
| if (b) { |
| if (!foo->has_value()) return; |
| // We have created `foo.value()`. |
| foo->value(); |
| } else { |
| if (!foo->has_value()) return; |
| // We have created `foo.value()` again, in a different environment. |
| foo->value(); |
| } |
| // Now we merge the two values. UncheckedOptionalAccessModel::merge() will |
| // throw away the "value" property. |
| foo->value(); |
| } |
| )"); |
| } |
| |
| // This test is aimed at the core model, not the diagnostic. It is a regression |
| // test against a crash when using non-trivial smart pointers, like |
| // `std::unique_ptr`. As such, it doesn't test the access itself, which would be |
| // ignored regardless because of `IgnoreSmartPointerDereference = true`, above. |
| TEST_P(UncheckedOptionalAccessTest, AssignThroughLvalueReferencePtr) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| template <typename T> |
| struct smart_ptr { |
| typename std::add_lvalue_reference<T>::type operator*() &; |
| }; |
| |
| void target() { |
| smart_ptr<$ns::$optional<int>> x; |
| // Verify that this assignment does not crash. |
| *x = 3; |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, CorrelatedBranches) { |
| ExpectDiagnosticsFor(R"code( |
| #include "unchecked_optional_access_test.h" |
| |
| void target(bool b, $ns::$optional<int> opt) { |
| if (b || opt.has_value()) { |
| if (!b) { |
| opt.value(); |
| } |
| } |
| } |
| )code"); |
| |
| ExpectDiagnosticsFor(R"code( |
| #include "unchecked_optional_access_test.h" |
| |
| void target(bool b, $ns::$optional<int> opt) { |
| if (b && !opt.has_value()) return; |
| if (b) { |
| opt.value(); |
| } |
| } |
| )code"); |
| |
| ExpectDiagnosticsFor( |
| R"code( |
| #include "unchecked_optional_access_test.h" |
| |
| void target(bool b, $ns::$optional<int> opt) { |
| if (opt.has_value()) b = true; |
| if (b) { |
| opt.value(); // [[unsafe]] |
| } |
| } |
| )code"); |
| |
| ExpectDiagnosticsFor(R"code( |
| #include "unchecked_optional_access_test.h" |
| |
| void target(bool b, $ns::$optional<int> opt) { |
| if (b) return; |
| if (opt.has_value()) b = true; |
| if (b) { |
| opt.value(); |
| } |
| } |
| )code"); |
| |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target(bool b, $ns::$optional<int> opt) { |
| if (opt.has_value() == b) { |
| if (b) { |
| opt.value(); |
| } |
| } |
| } |
| )"); |
| |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target(bool b, $ns::$optional<int> opt) { |
| if (opt.has_value() != b) { |
| if (!b) { |
| opt.value(); |
| } |
| } |
| } |
| )"); |
| |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target(bool b) { |
| $ns::$optional<int> opt1 = $ns::nullopt; |
| $ns::$optional<int> opt2; |
| if (b) { |
| opt2 = $ns::nullopt; |
| } else { |
| opt2 = $ns::nullopt; |
| } |
| if (opt2.has_value()) { |
| opt1.value(); |
| } |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, JoinDistinctValues) { |
| ExpectDiagnosticsFor( |
| R"code( |
| #include "unchecked_optional_access_test.h" |
| |
| void target(bool b) { |
| $ns::$optional<int> opt; |
| if (b) { |
| opt = Make<$ns::$optional<int>>(); |
| } else { |
| opt = Make<$ns::$optional<int>>(); |
| } |
| if (opt.has_value()) { |
| opt.value(); |
| } else { |
| opt.value(); // [[unsafe]] |
| } |
| } |
| )code"); |
| |
| ExpectDiagnosticsFor(R"code( |
| #include "unchecked_optional_access_test.h" |
| |
| void target(bool b) { |
| $ns::$optional<int> opt; |
| if (b) { |
| opt = Make<$ns::$optional<int>>(); |
| if (!opt.has_value()) return; |
| } else { |
| opt = Make<$ns::$optional<int>>(); |
| if (!opt.has_value()) return; |
| } |
| opt.value(); |
| } |
| )code"); |
| |
| ExpectDiagnosticsFor( |
| R"code( |
| #include "unchecked_optional_access_test.h" |
| |
| void target(bool b) { |
| $ns::$optional<int> opt; |
| if (b) { |
| opt = Make<$ns::$optional<int>>(); |
| if (!opt.has_value()) return; |
| } else { |
| opt = Make<$ns::$optional<int>>(); |
| } |
| opt.value(); // [[unsafe]] |
| } |
| )code"); |
| |
| ExpectDiagnosticsFor( |
| R"code( |
| #include "unchecked_optional_access_test.h" |
| |
| void target(bool b) { |
| $ns::$optional<int> opt; |
| if (b) { |
| opt = 1; |
| } else { |
| opt = 2; |
| } |
| opt.value(); |
| } |
| )code"); |
| |
| ExpectDiagnosticsFor( |
| R"code( |
| #include "unchecked_optional_access_test.h" |
| |
| void target(bool b) { |
| $ns::$optional<int> opt; |
| if (b) { |
| opt = 1; |
| } else { |
| opt = Make<$ns::$optional<int>>(); |
| } |
| opt.value(); // [[unsafe]] |
| } |
| )code"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, AccessValueInLoop) { |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt = 3; |
| while (Make<bool>()) { |
| opt.value(); |
| } |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, ReassignValueInLoopWithCheckSafe) { |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt = 3; |
| while (Make<bool>()) { |
| opt.value(); |
| |
| opt = Make<$ns::$optional<int>>(); |
| if (!opt.has_value()) return; |
| } |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, ReassignValueInLoopNoCheckUnsafe) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt = 3; |
| while (Make<bool>()) { |
| opt.value(); // [[unsafe]] |
| |
| opt = Make<$ns::$optional<int>>(); |
| } |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, ReassignValueInLoopToUnsetUnsafe) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt = 3; |
| while (Make<bool>()) |
| opt = $ns::nullopt; |
| $ns::$optional<int> opt2 = $ns::nullopt; |
| if (opt.has_value()) |
| opt2 = $ns::$optional<int>(3); |
| opt2.value(); // [[unsafe]] |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, ReassignValueInLoopToSetUnsafe) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt = $ns::nullopt; |
| while (Make<bool>()) |
| opt = $ns::$optional<int>(3); |
| $ns::$optional<int> opt2 = $ns::nullopt; |
| if (!opt.has_value()) |
| opt2 = $ns::$optional<int>(3); |
| opt2.value(); // [[unsafe]] |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, ReassignValueInLoopToUnknownUnsafe) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt = $ns::nullopt; |
| while (Make<bool>()) |
| opt = Make<$ns::$optional<int>>(); |
| $ns::$optional<int> opt2 = $ns::nullopt; |
| if (!opt.has_value()) |
| opt2 = $ns::$optional<int>(3); |
| opt2.value(); // [[unsafe]] |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, ReassignValueInLoopBadConditionUnsafe) { |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| void target() { |
| $ns::$optional<int> opt = 3; |
| while (Make<bool>()) { |
| opt.value(); // [[unsafe]] |
| |
| opt = Make<$ns::$optional<int>>(); |
| if (!opt.has_value()) continue; |
| } |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, StructuredBindingsFromStruct) { |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct kv { $ns::$optional<int> opt; int x; }; |
| int target() { |
| auto [contents, x] = Make<kv>(); |
| return contents ? *contents : x; |
| } |
| )"); |
| |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| template <typename T1, typename T2> |
| struct pair { T1 fst; T2 snd; }; |
| int target() { |
| auto [contents, x] = Make<pair<$ns::$optional<int>, int>>(); |
| return contents ? *contents : x; |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, StructuredBindingsFromTupleLikeType) { |
| ExpectDiagnosticsFor(R"( |
| #include "unchecked_optional_access_test.h" |
| |
| namespace std { |
| template <class> struct tuple_size; |
| template <size_t, class> struct tuple_element; |
| template <class...> class tuple; |
| |
| template <class... T> |
| struct tuple_size<tuple<T...>> : integral_constant<size_t, sizeof...(T)> {}; |
| |
| template <size_t I, class... T> |
| struct tuple_element<I, tuple<T...>> { |
| using type = __type_pack_element<I, T...>; |
| }; |
| |
| template <class...> class tuple {}; |
| template <size_t I, class... T> |
| typename tuple_element<I, tuple<T...>>::type get(tuple<T...>); |
| } // namespace std |
| |
| std::tuple<$ns::$optional<const char *>, int> get_opt(); |
| void target() { |
| auto [content, ck] = get_opt(); |
| content ? *content : ""; |
| } |
| )"); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, CtorInitializerNullopt) { |
| using namespace ast_matchers; |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct Target { |
| Target(): opt($ns::nullopt) { |
| opt.value(); // [[unsafe]] |
| } |
| $ns::$optional<int> opt; |
| }; |
| )", |
| cxxConstructorDecl(ofClass(hasName("Target")))); |
| } |
| |
| TEST_P(UncheckedOptionalAccessTest, CtorInitializerValue) { |
| using namespace ast_matchers; |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| |
| struct Target { |
| Target(): opt(3) { |
| opt.value(); |
| } |
| $ns::$optional<int> opt; |
| }; |
| )", |
| cxxConstructorDecl(ofClass(hasName("Target")))); |
| } |
| |
| // This is regression test, it shouldn't crash. |
| TEST_P(UncheckedOptionalAccessTest, Bitfield) { |
| using namespace ast_matchers; |
| ExpectDiagnosticsFor( |
| R"( |
| #include "unchecked_optional_access_test.h" |
| struct Dst { |
| unsigned int n : 1; |
| }; |
| void target() { |
| $ns::$optional<bool> v; |
| Dst d; |
| if (v.has_value()) |
| d.n = v.value(); |
| } |
| )"); |
| } |
| // FIXME: Add support for: |
| // - constructors (copy, move) |
| // - assignment operators (default, copy, move) |
| // - invalidation (passing optional by non-const reference/pointer) |