llvm-project/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
Yitzhak Mandelbaum 5d22d1f548 [clang][dataflow] Improve optional model's support for ignoring smart pointers.
The optional model has an option to ignore optionals accessed through smart
pointer types (other than optional itself). This patch improves this feature in
two ways:

1. We extend support to optionals accessed directly through the smart pointer,
like `ptr->value()`. Previously, support was limited to cases that went through
an intermediate field.

2. We clean up the implementation by removing the option from the analysis,
leaving it only in the diagnostic phase (where it is relevant).

3. Adjusts a test which was misleading in what it was testing.

Differential Revision: https://reviews.llvm.org/D140020
2022-12-15 15:39:52 +00:00

2946 lines
76 KiB
C++

//===- 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/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.h"
#include "clang/Basic/SourceLocation.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 <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;
};
class UncheckedOptionalAccessTest
: public ::testing::TestWithParam<OptionalTypeIdentifier> {
protected:
void ExpectDiagnosticsFor(std::string SourceCode) {
ExpectDiagnosticsFor(SourceCode, ast_matchers::hasName("target"));
}
private:
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) {
DiagnosticLines.insert(SrcMgr.getPresumedLineNumber(Loc));
}
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;
});
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, 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, ValueOrComparison) {
// Pointers.
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");
// Integers.
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");
// Strings.
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");
// Pointer-to-optional.
//
// 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, 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) {
// FIXME: Fix when to initialize `value`. All unwrapping should be safe in
// this example, but `value` initialization is done multiple times during the
// fixpoint iterations and joining the environment won't correctly merge them.
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(); // [[unsafe]]
}
)");
}
// 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();
}
}
)");
// FIXME: Add support for operator==.
// ExpectDiagnosticsFor(R"(
// #include "unchecked_optional_access_test.h"
//
// void target($ns::$optional<int> opt1, $ns::$optional<int> opt2) {
// if (opt1 == opt2) {
// if (opt1.has_value()) {
// opt2.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, ReassignValueInLoop) {
ExpectDiagnosticsFor(R"(
#include "unchecked_optional_access_test.h"
void target() {
$ns::$optional<int> opt = 3;
while (Make<bool>()) {
opt.value();
}
}
)");
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;
}
}
)");
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>>();
}
}
)");
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 : "";
}
)");
}
// FIXME: Add support for:
// - constructors (copy, move)
// - assignment operators (default, copy, move)
// - invalidation (passing optional by non-const reference/pointer)