https://github.com/fmayer updated https://github.com/llvm/llvm-project/pull/171188
>From 3a7705624359678edaed5c7b9686cae034cb4bfd Mon Sep 17 00:00:00 2001 From: Florian Mayer <[email protected]> Date: Mon, 8 Dec 2025 13:10:30 -0800 Subject: [PATCH 1/4] change Created using spr 1.3.7 --- .../clang-tidy/abseil/UncheckedStatusOrAccessCheck.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang-tools-extra/clang-tidy/abseil/UncheckedStatusOrAccessCheck.h b/clang-tools-extra/clang-tidy/abseil/UncheckedStatusOrAccessCheck.h index cf47703f0a972..8fefee4691be6 100644 --- a/clang-tools-extra/clang-tidy/abseil/UncheckedStatusOrAccessCheck.h +++ b/clang-tools-extra/clang-tidy/abseil/UncheckedStatusOrAccessCheck.h @@ -10,7 +10,7 @@ namespace clang::tidy::abseil { // assuring that it contains a value. // // For details on the dataflow analysis implemented in this check see: -// http://google3/devtools/cymbal/nullability/statusor +// clang/lib/Analysis/FlowSensitive/Models/UncheckedStatusOrAccessModel.cpp class UncheckedStatusOrAccessCheck : public ClangTidyCheck { public: using ClangTidyCheck::ClangTidyCheck; >From d020c4b77da52906b21b382650e664439c3aaa65 Mon Sep 17 00:00:00 2001 From: Florian Mayer <[email protected]> Date: Mon, 8 Dec 2025 17:49:15 -0800 Subject: [PATCH 2/4] test Created using spr 1.3.7 --- .../abseil/Inputs/absl/meta/type_traits.h | 46 ++ .../abseil/Inputs/absl/status/status.h | 69 +++ .../abseil/Inputs/absl/status/statusor.h | 346 ++++++++++++++ .../checkers/abseil/Inputs/cstddef.h | 10 + .../checkers/abseil/Inputs/initializer_list | 11 + .../checkers/abseil/Inputs/type_traits | 427 ++++++++++++++++++ .../abseil-unchecked-statusor-access.cpp | 138 ++++++ 7 files changed, 1047 insertions(+) create mode 100644 clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/absl/meta/type_traits.h create mode 100644 clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/absl/status/status.h create mode 100644 clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/absl/status/statusor.h create mode 100644 clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/cstddef.h create mode 100644 clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/initializer_list create mode 100644 clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/type_traits create mode 100644 clang-tools-extra/test/clang-tidy/checkers/abseil/abseil-unchecked-statusor-access.cpp diff --git a/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/absl/meta/type_traits.h b/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/absl/meta/type_traits.h new file mode 100644 index 0000000000000..06ce61dbcc1e7 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/absl/meta/type_traits.h @@ -0,0 +1,46 @@ +#include <type_traits> + +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... Ts> +struct disjunction : std::false_type {}; + +template <typename T, typename... Ts> +struct disjunction<T, Ts...> + : std::conditional<T::value, T, disjunction<Ts...>>::type {}; + +template <typename T> +struct disjunction<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; + + +template <bool B, typename T, typename F> +using conditional_t = typename std::conditional<B, T, F>::type; + +template <typename T> +using remove_cv_t = typename std::remove_cv<T>::type; + +template <typename T> +using remove_reference_t = typename std::remove_reference<T>::type; + +template <typename T> +using decay_t = typename std::decay<T>::type; + +using std::in_place; +using std::in_place_t; +} // namespace absl diff --git a/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/absl/status/status.h b/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/absl/status/status.h new file mode 100644 index 0000000000000..fd0910e81436a --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/absl/status/status.h @@ -0,0 +1,69 @@ +namespace absl { +struct SourceLocation { + static constexpr SourceLocation current(); + static constexpr SourceLocation + DoNotInvokeDirectlyNoSeriouslyDont(int line, const char *file_name); +}; +} // namespace absl +namespace absl { +enum class StatusCode : int { + kOk, + kCancelled, + kUnknown, + kInvalidArgument, + kDeadlineExceeded, + kNotFound, + kAlreadyExists, + kPermissionDenied, + kResourceExhausted, + kFailedPrecondition, + kAborted, + kOutOfRange, + kUnimplemented, + kInternal, + kUnavailable, + kDataLoss, + kUnauthenticated, +}; +} // namespace absl + +namespace absl { +enum class StatusToStringMode : int { + kWithNoExtraData = 0, + kWithPayload = 1 << 0, + kWithSourceLocation = 1 << 1, + kWithEverything = ~kWithNoExtraData, + kDefault = kWithPayload, +}; +class Status { +public: + Status(); + Status(const Status &base_status, absl::SourceLocation loc); + Status(Status &&base_status, absl::SourceLocation loc); + ~Status() {} + + Status(const Status &); + Status &operator=(const Status &x); + + Status(Status &&) noexcept; + Status &operator=(Status &&); + + friend bool operator==(const Status &, const Status &); + friend bool operator!=(const Status &, const Status &); + + bool ok() const { return true; } + void CheckSuccess() const; + void IgnoreError() const; + int error_code() const; + absl::Status ToCanonical() const; + void Update(const Status &new_status); + void Update(Status &&new_status); +}; + +bool operator==(const Status &lhs, const Status &rhs); +bool operator!=(const Status &lhs, const Status &rhs); + +Status OkStatus(); +Status InvalidArgumentError(const char *); + +} // namespace absl diff --git a/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/absl/status/statusor.h b/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/absl/status/statusor.h new file mode 100644 index 0000000000000..0151dda0cb97d --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/absl/status/statusor.h @@ -0,0 +1,346 @@ +#include "status.h" +#include <absl/meta/type_traits.h> +#include <initializer_list> + +namespace absl { + +template <typename T> struct StatusOr; + +namespace internal_statusor { + +template <typename T, typename U, typename = void> +struct HasConversionOperatorToStatusOr : std::false_type {}; + +template <typename T, typename U> +void test(char (*)[sizeof(std::declval<U>().operator absl::StatusOr<T>())]); + +template <typename T, typename U> +struct HasConversionOperatorToStatusOr<T, U, decltype(test<T, U>(0))> + : std::true_type {}; + +template <typename T, typename U> +using IsConstructibleOrConvertibleFromStatusOr = + absl::disjunction<std::is_constructible<T, StatusOr<U> &>, + std::is_constructible<T, const StatusOr<U> &>, + std::is_constructible<T, StatusOr<U> &&>, + std::is_constructible<T, const StatusOr<U> &&>, + std::is_convertible<StatusOr<U> &, T>, + std::is_convertible<const StatusOr<U> &, T>, + std::is_convertible<StatusOr<U> &&, T>, + std::is_convertible<const StatusOr<U> &&, T>>; + +template <typename T, typename U> +using IsConstructibleOrConvertibleOrAssignableFromStatusOr = + absl::disjunction<IsConstructibleOrConvertibleFromStatusOr<T, U>, + std::is_assignable<T &, StatusOr<U> &>, + std::is_assignable<T &, const StatusOr<U> &>, + std::is_assignable<T &, StatusOr<U> &&>, + std::is_assignable<T &, const StatusOr<U> &&>>; + +template <typename T, typename U> +struct IsDirectInitializationAmbiguous + : public absl::conditional_t< + std::is_same<absl::remove_cv_t<absl::remove_reference_t<U>>, + U>::value, + std::false_type, + IsDirectInitializationAmbiguous< + T, absl::remove_cv_t<absl::remove_reference_t<U>>>> {}; + +template <typename T, typename V> +struct IsDirectInitializationAmbiguous<T, absl::StatusOr<V>> + : public IsConstructibleOrConvertibleFromStatusOr<T, V> {}; + +template <typename T, typename U> +using IsDirectInitializationValid = absl::disjunction< + // Short circuits if T is basically U. + std::is_same<T, absl::remove_cv_t<absl::remove_reference_t<U>>>, + absl::negation<absl::disjunction< + std::is_same<absl::StatusOr<T>, + absl::remove_cv_t<absl::remove_reference_t<U>>>, + std::is_same<absl::Status, + absl::remove_cv_t<absl::remove_reference_t<U>>>, + std::is_same<absl::in_place_t, + absl::remove_cv_t<absl::remove_reference_t<U>>>, + IsDirectInitializationAmbiguous<T, U>>>>; + +template <typename T, typename U> +struct IsForwardingAssignmentAmbiguous + : public absl::conditional_t< + std::is_same<absl::remove_cv_t<absl::remove_reference_t<U>>, + U>::value, + std::false_type, + IsForwardingAssignmentAmbiguous< + T, absl::remove_cv_t<absl::remove_reference_t<U>>>> {}; + +template <typename T, typename U> +struct IsForwardingAssignmentAmbiguous<T, absl::StatusOr<U>> + : public IsConstructibleOrConvertibleOrAssignableFromStatusOr<T, U> {}; + +template <typename T, typename U> +using IsForwardingAssignmentValid = absl::disjunction< + // Short circuits if T is basically U. + std::is_same<T, absl::remove_cv_t<absl::remove_reference_t<U>>>, + absl::negation<absl::disjunction< + std::is_same<absl::StatusOr<T>, + absl::remove_cv_t<absl::remove_reference_t<U>>>, + std::is_same<absl::Status, + absl::remove_cv_t<absl::remove_reference_t<U>>>, + std::is_same<absl::in_place_t, + absl::remove_cv_t<absl::remove_reference_t<U>>>, + IsForwardingAssignmentAmbiguous<T, U>>>>; + +template <typename T, typename U> +using IsForwardingAssignmentValid = absl::disjunction< + // Short circuits if T is basically U. + std::is_same<T, absl::remove_cv_t<absl::remove_reference_t<U>>>, + absl::negation<absl::disjunction< + std::is_same<absl::StatusOr<T>, + absl::remove_cv_t<absl::remove_reference_t<U>>>, + std::is_same<absl::Status, + absl::remove_cv_t<absl::remove_reference_t<U>>>, + std::is_same<absl::in_place_t, + absl::remove_cv_t<absl::remove_reference_t<U>>>, + IsForwardingAssignmentAmbiguous<T, U>>>>; + +template <typename T> struct OperatorBase { + const T &value() const &; + T &value() &; + const T &&value() const &&; + T &&value() &&; + + const T &operator*() const &; + T &operator*() &; + const T &&operator*() const &&; + T &&operator*() &&; + + // To test that analyses are okay if there is a use of operator* + // within this base class. + const T *operator->() const { return __builtin_addressof(**this); } + T *operator->() { return __builtin_addressof(**this); } +}; + +} // namespace internal_statusor + +template <typename T> +struct StatusOr : private internal_statusor::OperatorBase<T> { + explicit StatusOr(); + + StatusOr(const StatusOr &) = default; + StatusOr &operator=(const StatusOr &) = default; + + StatusOr(StatusOr &&) = default; + StatusOr &operator=(StatusOr &&) = default; + + template < + typename U, + absl::enable_if_t< + absl::conjunction< + absl::negation<std::is_same<T, U>>, + std::is_constructible<T, const U &>, + std::is_convertible<const U &, T>, + absl::negation< + internal_statusor::IsConstructibleOrConvertibleFromStatusOr< + T, U>>>::value, + int> = 0> + StatusOr(const StatusOr<U> &); + + template < + typename U, + absl::enable_if_t< + absl::conjunction< + absl::negation<std::is_same<T, U>>, + std::is_constructible<T, const U &>, + absl::negation<std::is_convertible<const U &, T>>, + absl::negation< + internal_statusor::IsConstructibleOrConvertibleFromStatusOr< + T, U>>>::value, + int> = 0> + explicit StatusOr(const StatusOr<U> &); + + template < + typename U, + absl::enable_if_t< + absl::conjunction< + absl::negation<std::is_same<T, U>>, + std::is_constructible<T, U &&>, std::is_convertible<U &&, T>, + absl::negation< + internal_statusor::IsConstructibleOrConvertibleFromStatusOr< + T, U>>>::value, + int> = 0> + StatusOr(StatusOr<U> &&); + + template < + typename U, + absl::enable_if_t< + absl::conjunction< + absl::negation<std::is_same<T, U>>, + std::is_constructible<T, U &&>, + absl::negation<std::is_convertible<U &&, T>>, + absl::negation< + internal_statusor::IsConstructibleOrConvertibleFromStatusOr< + T, U>>>::value, + int> = 0> + explicit StatusOr(StatusOr<U> &&); + + template < + typename U, + absl::enable_if_t< + absl::conjunction< + absl::negation<std::is_same<T, U>>, + std::is_constructible<T, const U &>, + std::is_assignable<T, const U &>, + absl::negation< + internal_statusor:: + IsConstructibleOrConvertibleOrAssignableFromStatusOr< + T, U>>>::value, + int> = 0> + StatusOr &operator=(const StatusOr<U> &); + + template < + typename U, + absl::enable_if_t< + absl::conjunction< + absl::negation<std::is_same<T, U>>, + std::is_constructible<T, U &&>, std::is_assignable<T, U &&>, + absl::negation< + internal_statusor:: + IsConstructibleOrConvertibleOrAssignableFromStatusOr< + T, U>>>::value, + int> = 0> + StatusOr &operator=(StatusOr<U> &&); + + template < + typename U = absl::Status, + absl::enable_if_t< + absl::conjunction< + std::is_convertible<U &&, absl::Status>, + std::is_constructible<absl::Status, U &&>, + absl::negation<std::is_same<absl::decay_t<U>, absl::StatusOr<T>>>, + absl::negation<std::is_same<absl::decay_t<U>, T>>, + absl::negation<std::is_same<absl::decay_t<U>, absl::in_place_t>>, + absl::negation<internal_statusor::HasConversionOperatorToStatusOr< + T, U &&>>>::value, + int> = 0> + StatusOr(U &&); + + template < + typename U = absl::Status, + absl::enable_if_t< + absl::conjunction< + absl::negation<std::is_convertible<U &&, absl::Status>>, + std::is_constructible<absl::Status, U &&>, + absl::negation<std::is_same<absl::decay_t<U>, absl::StatusOr<T>>>, + absl::negation<std::is_same<absl::decay_t<U>, T>>, + absl::negation<std::is_same<absl::decay_t<U>, absl::in_place_t>>, + absl::negation<internal_statusor::HasConversionOperatorToStatusOr< + T, U &&>>>::value, + int> = 0> + explicit StatusOr(U &&); + + template < + typename U = absl::Status, + absl::enable_if_t< + absl::conjunction< + std::is_convertible<U &&, absl::Status>, + std::is_constructible<absl::Status, U &&>, + absl::negation<std::is_same<absl::decay_t<U>, absl::StatusOr<T>>>, + absl::negation<std::is_same<absl::decay_t<U>, T>>, + absl::negation<std::is_same<absl::decay_t<U>, absl::in_place_t>>, + absl::negation<internal_statusor::HasConversionOperatorToStatusOr< + T, U &&>>>::value, + int> = 0> + StatusOr &operator=(U &&); + + template < + typename U = T, + typename = typename std::enable_if<absl::conjunction< + std::is_constructible<T, U &&>, std::is_assignable<T &, U &&>, + absl::disjunction< + std::is_same<absl::remove_cv_t<absl::remove_reference_t<U>>, T>, + absl::conjunction< + absl::negation<std::is_convertible<U &&, absl::Status>>, + absl::negation< + internal_statusor::HasConversionOperatorToStatusOr< + T, U &&>>>>, + internal_statusor::IsForwardingAssignmentValid<T, U &&>>::value>:: + type> + StatusOr &operator=(U &&); + + template <typename... Args> explicit StatusOr(absl::in_place_t, Args &&...); + + template <typename U, typename... Args> + explicit StatusOr(absl::in_place_t, std::initializer_list<U>, Args &&...); + + template < + typename U = T, + absl::enable_if_t< + absl::conjunction< + internal_statusor::IsDirectInitializationValid<T, U &&>, + std::is_constructible<T, U &&>, std::is_convertible<U &&, T>, + absl::disjunction< + std::is_same<absl::remove_cv_t<absl::remove_reference_t<U>>, + T>, + absl::conjunction< + absl::negation<std::is_convertible<U &&, absl::Status>>, + absl::negation< + internal_statusor::HasConversionOperatorToStatusOr< + T, U &&>>>>>::value, + int> = 0> + StatusOr(U &&); + + template < + typename U = T, + absl::enable_if_t< + absl::conjunction< + internal_statusor::IsDirectInitializationValid<T, U &&>, + absl::disjunction< + std::is_same<absl::remove_cv_t<absl::remove_reference_t<U>>, + T>, + absl::conjunction< + absl::negation<std::is_constructible<absl::Status, U &&>>, + absl::negation< + internal_statusor::HasConversionOperatorToStatusOr< + T, U &&>>>>, + std::is_constructible<T, U &&>, + absl::negation<std::is_convertible<U &&, T>>>::value, + int> = 0> + explicit StatusOr(U &&); + + bool ok() const; + + const Status &status() const & { return status_; } + Status status() &&; + + using StatusOr::OperatorBase::value; + + const T &ValueOrDie() const &; + T &ValueOrDie() &; + const T &&ValueOrDie() const &&; + T &&ValueOrDie() &&; + + using StatusOr::OperatorBase::operator*; + using StatusOr::OperatorBase::operator->; + + template <typename U> T value_or(U &&default_value) const &; + template <typename U> T value_or(U &&default_value) &&; + + template <typename... Args> T &emplace(Args &&...args); + + template < + typename U, typename... Args, + absl::enable_if_t<std::is_constructible<T, std::initializer_list<U> &, + Args &&...>::value, + int> = 0> + T &emplace(std::initializer_list<U> ilist, Args &&...args); + +private: + absl::Status status_; +}; + +template <typename T> +bool operator==(const StatusOr<T> &lhs, const StatusOr<T> &rhs); + +template <typename T> +bool operator!=(const StatusOr<T> &lhs, const StatusOr<T> &rhs); + +} // namespace absl diff --git a/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/cstddef.h b/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/cstddef.h new file mode 100644 index 0000000000000..633260f24f99b --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/cstddef.h @@ -0,0 +1,10 @@ +namespace std { + +typedef decltype(sizeof(char)) size_t; + +using nullptr_t = decltype(nullptr); + +} // namespace std + +typedef decltype(sizeof(char)) size_t; +typedef decltype(sizeof(char*)) ptrdiff_t; diff --git a/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/initializer_list b/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/initializer_list new file mode 100644 index 0000000000000..886a54fe217f4 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/initializer_list @@ -0,0 +1,11 @@ + +namespace std { + +template <typename T> +class initializer_list { + public: + const T *a, *b; + initializer_list() noexcept; +}; + +} // namespace std \ No newline at end of file diff --git a/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/type_traits b/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/type_traits new file mode 100644 index 0000000000000..c97ae9c2d14bd --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/abseil/Inputs/type_traits @@ -0,0 +1,427 @@ +#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 {}; + +struct in_place_t {}; + +constexpr in_place_t in_place; + +} // namespace std diff --git a/clang-tools-extra/test/clang-tidy/checkers/abseil/abseil-unchecked-statusor-access.cpp b/clang-tools-extra/test/clang-tidy/checkers/abseil/abseil-unchecked-statusor-access.cpp new file mode 100644 index 0000000000000..865c5aa1124d3 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/abseil/abseil-unchecked-statusor-access.cpp @@ -0,0 +1,138 @@ +// RUN: %check_clang_tidy %s abseil-unchecked-statusor-access %t -- -header-filter='' -- -I %S/Inputs + +#include "absl/status/statusor.h" +void unchecked_value_access(const absl::StatusOr<int>& sor) { + sor.value(); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: unchecked access to 'absl::StatusOr' value [abseil-unchecked-statusor-access] +} + +void unchecked_value_or_die_access(const absl::StatusOr<int>& sor) { + sor.ValueOrDie(); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: unchecked access to 'absl::StatusOr' value [abseil-unchecked-statusor-access] +} + +void unchecked_deref_operator_access(const absl::StatusOr<int>& sor) { + *sor; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: unchecked access to 'absl::StatusOr' value [abseil-unchecked-statusor-access] +} + +struct Foo { + void foo() const {} +}; + +void unchecked_arrow_operator_access(const absl::StatusOr<Foo>& sor) { + sor->foo(); + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: unchecked access to 'absl::StatusOr' value [abseil-unchecked-statusor-access] +} + +void f2(const absl::StatusOr<int>& sor) { + if (sor.ok()) { + sor.value(); + } +} + +template <typename T> +void function_template_without_user(const absl::StatusOr<T>& sor) { + sor.value(); // no-warning +} + +template <typename T> +void function_template_with_user(const absl::StatusOr<T>& sor) { + sor.value(); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: unchecked access to 'absl::StatusOr' value [abseil-unchecked-statusor-access] +} + +void function_template_user(const absl::StatusOr<int>& sor) { + // Instantiate the f3 function template so that it gets matched by the check. + function_template_with_user(sor); +} + +template<typename T> +void function_template_with_specialization(const absl::StatusOr<int>& sor) { + sor.value(); // no-warning +} + +template<> +void function_template_with_specialization<int>(const absl::StatusOr<int>& sor) { + sor.value(); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: unchecked access to 'absl::StatusOr' value [abseil-unchecked-statusor-access] +} + + +template <typename T> +class ClassTemplateWithSpecializations { + void f(const absl::StatusOr<int>& sor) { + sor.value(); // no-warning + } +}; + +template<typename T> +class ClassTemplateWithSpecializations<T*> { + void f(const absl::StatusOr<int>& sor) { + sor.value(); // no-warning + } +}; + +template<> +class ClassTemplateWithSpecializations<int> { + void f(const absl::StatusOr<int>& sor) { + sor.value(); + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: unchecked access to 'absl::StatusOr' value [abseil-unchecked-statusor-access] + } +}; + +// The templates below are not instantiated and CFGs can not be properly built +// for them. They are here to make sure that the checker does not crash, but +// instead ignores non-instantiated templates. + +template <typename T> +struct C1 {}; + +template <typename T> +struct C2 : public C1<T> { + ~C2() {} +}; + +template <typename T, template <class> class B> +struct C3 : public B<T> { + ~C3() {} +}; + +void multiple_unchecked_accesses(absl::StatusOr<int> sor1, + absl::StatusOr<int> sor2) { + for (int i = 0; i < 10; i++) { + sor1.ValueOrDie(); + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: unchecked access to 'absl::StatusOr' value [abseil-unchecked-statusor-access] + } + sor2.ValueOrDie(); + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: unchecked access to 'absl::StatusOr' value [abseil-unchecked-statusor-access] +} + +class C4 { + explicit C4(absl::StatusOr<int> sor) : foo_(sor.value()) { + // CHECK-MESSAGES: :[[@LINE-1]]:51: warning: unchecked access to 'absl::StatusOr' value [abseil-unchecked-statusor-access] + } + int foo_; +}; + +void lambda(const absl::StatusOr<int>& sor) { + [&sor]() { + sor.value(); + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: unchecked access to 'absl::StatusOr' value [abseil-unchecked-statusor-access] + }(); + + [&]() { + if (sor.ok()) { + sor.value(); + } + }(); + + // Information from the surrounding context is not propagated through the + // lambda. + if (sor.ok()) { + [&sor]() { + sor.value(); + // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: unchecked access to 'absl::StatusOr' value [abseil-unchecked-statusor-access] + }(); + } +} >From 6580c094fff2ff4189c8b1d4c67a420e05316f9a Mon Sep 17 00:00:00 2001 From: Florian Mayer <[email protected]> Date: Mon, 8 Dec 2025 18:10:48 -0800 Subject: [PATCH 3/4] doc Created using spr 1.3.7 --- .../abseil/unchecked-statusor-access.rst | 377 ++++++++++++++++++ 1 file changed, 377 insertions(+) create mode 100644 clang-tools-extra/docs/clang-tidy/checks/abseil/unchecked-statusor-access.rst diff --git a/clang-tools-extra/docs/clang-tidy/checks/abseil/unchecked-statusor-access.rst b/clang-tools-extra/docs/clang-tidy/checks/abseil/unchecked-statusor-access.rst new file mode 100644 index 0000000000000..95f534c390901 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/abseil/unchecked-statusor-access.rst @@ -0,0 +1,377 @@ +.. title:: clang-tidy - abseil-unchecked-statusor-access + +abseil-unchecked-statusor-access +================================ + +This check identifies unsafe accesses to values contained in +``absl::StatusOr<T>`` objects. Below we will refer to this type as +``StatusOr<T>``. + +An access to the value of an ``StatusOr<T>`` occurs when one of its +``value``, ``operator*``, or ``operator->`` member functions is invoked. +To align with common misconceptions, the check considers these member +functions as equivalent, even though there are subtle differences +related to exceptions vs. undefined behavior. + +An access to the value of a ``StatusOr<T>`` is considered safe if and +only if code in the local scope (e.g. function body) ensures that the +status of the ``StatusOr<T>`` is ok in all possible execution paths that +can reach the access. That should happen either through an explicit +check, using the ``StatusOr<T>::ok`` member function, or by constructing +the ``StatusOr<T>`` in a way that shows that its status is unambiguously +ok (e.g. by passing a value to its constructor). + +Below we list some examples of safe and unsafe ``StatusOr<T>`` access +patterns. + +Note: If the check isn’t behaving as you would have expected on a code +snippet, please `report it <http://github.com/llvm/llvm-project/issues/new>`__. + +False negatives +--------------- + +This check generally does **not** generate false negatives. If it cannot +prove an access safe, it is assumed to be unsafe. That being said, there +are some heuristics used that in very rare cases might be incorrect: + +- `a const method accessor (without arguments) that returns different + values when called multiple times <#functionstability>`__. + +If you think the check generated a false negative, please `report +it <http://github.com/llvm/llvm-project/issues/new>`__. + +Known limitations +----------------- + +This is a non-exhaustive list of constructs that are currently not +modelled in the check and will lead to false positives: + +- `Checking a StatusOr and then capturing it in a lambda <#lambdas>`__ +- `Indexing into a container with the same index <#containers>`__ +- `Project specific helper-functions <#uncommonapi>`__, +- `Functions with a stable return value <#functionstability>`__ +- **Any** `cross-function reasoning <#crossfunction>`__. This is by + design and will not change in the future. + +Checking if the status is ok, then accessing the value +------------------------------------------------------ + +The check recognizes all straightforward ways for checking the status +and accessing the value contained in a ``StatusOr<T>`` object. For +example: + +.. code:: cpp + + void f(absl::StatusOr<int> sor) { + if (sor.ok()) { + use(*sor); + } + } + +Checking if the status is ok, then accessing the value from a copy +------------------------------------------------------------------ + +The criteria that the check uses is semantic, not syntactic. It +recognizes when a copy of the ``StatusOr<T>`` object being accessed is +known to have ok status. For example: + +.. code:: cpp + + void f(absl::StatusOr<int> sor1) { + if (sor1.ok()) { + absl::optional<int> sor2 = sor1; + use(*sor2); + } + } + +Ensuring that the status is ok using common macros +-------------------------------------------------- + +The check is aware of common macros like ``ABSL_CHECK`` and ``ASSERT_THAT``. +Those can be used to ensure that the status of a ``StatusOr<T>`` object +is ok. For example: + +.. code:: cpp + + void f(absl::StatusOr<int> sor) { + ABSL_DCHECK_OK(sor); + use(*sor); + } + +Ensuring that the status is ok, then accessing the value in a correlated branch +------------------------------------------------------------------------------- + +The check is aware of correlated branches in the code and can figure out +when a ``StatusOr<T>`` object is ensured to have ok status on all +execution paths that lead to an access. For example: + +.. code:: cpp + + void f(absl::StatusOr<int> sor) { + bool safe = false; + if (sor.ok() && SomeOtherCondition()) { + safe = true; + } + // ... more code... + if (safe) { + use(*sor); + } + } + +Accessing the value without checking the status +----------------------------------------------- + +The check flags accesses to the value that are not locally guarded by a +status check: + +.. code:: cpp + + void f1(absl::StatusOr<int> sor) { + use(*sor); // unsafe: it is unclear whether the status of `sor` is ok. + } + + void f2(absl::StatusOr<MyStruct> sor) { + use(sor->member); // unsafe: it is unclear whether the status of `sor` is ok. + } + + void f3(absl::StatusOr<int> sor) { + use(sor.value()); // unsafe: it is unclear whether the status of `sor` is ok. + } + +Use ``ABSL_CHECK_OK`` to signal that you knowingly want to crash on +non-OK values. + +NOTE: Even though using ``.value()`` on a ``nullopt`` is defined to crash, +it is often unintentional. That is why our checker flags those as well. + +Accessing the value in the wrong branch +--------------------------------------- + +The check is aware of the state of a ``StatusOr<T>`` object in different +branches of the code. For example: + +.. code:: cpp + + void f(absl::StatusOr<int> sor) { + if (sor.ok()) { + } else { + use(*sor); // unsafe: it is clear that the status of `sor` is *not* ok. + } + } + +.. _functionstability: + +Assuming a function result to be stable +--------------------------------------- + +The check is aware that function results might not be stable. That is, +consecutive calls to the same function might return different values. +For example: + +.. code:: cpp + + void f(Foo foo) { + if (foo.sor().ok()) { + use(*foo.sor()); // unsafe: it is unclear whether the status of `foo.sor()` is ok. + } + } + +In such cases it is best to store the result of the function call in a +local variable and use it to access the value. For example: + +.. code:: cpp + + void f(Foo foo) { + if (const auto& foo_sor = foo.sor(); foo_sor.ok()) { + use(*foo_sor); + } + } + +The check **does** assume that ``const``-qualified accessor functions +return a stable value if no non-const function was called between the +two calls: + +.. code:: cpp + + class Foo { + const absl::StatusOr<int>& get() const { + [...]; + } + } + void f(Foo foo) { + if (foo.get().ok()) { + use(*get.get()); + } + } + +If there is a call to a non-``const``-qualified function, the check +assumes the return value of the accessor was mutated. + +.. code:: cpp + + class Foo { + const absl::StatusOr<int>& get() const { + [...]; + } + void mutate(); + } + void f(Foo foo) { + if (foo.get().ok()) { + foo.mutate(); + use(*get.get()); // unsafe: mutate might have changed the state of the object + } + } + +.. _uncommonapi: + +Relying on invariants of uncommon APIs +-------------------------------------- + +The check is unaware of invariants of uncommon APIs. For example: + +.. code:: cpp + + void f(Foo foo) { + if (foo.HasProperty("bar")) { + use(*foo.GetProperty("bar")); // unsafe: it is unclear whether the status of `foo.GetProperty("bar")` is ok. + } + } + +In such cases it is best to check explicitly that the status of the +``StatusOr<T>`` object is ok. For example: + +.. code:: cpp + + void f(Foo foo) { + if (const auto& property = foo.GetProperty("bar"); property.ok()) { + use(*property); + } + } + +.. _crossfunction: + +Checking if the status is ok, then passing the ``StatusOr<T>`` to another function +---------------------------------------------------------------------------------- + +The check relies on local reasoning. The check and value access must +both happen in the same function. An access is considered unsafe even if +the caller of the function performing the access ensures that the status +of the ``StatusOr<T>`` is ok. For example: + +.. code:: cpp + + void g(absl::StatusOr<int> sor) { + use(*sor); // unsafe: it is unclear whether the status of `sor` is ok. + } + + void f(absl::StatusOr<int> sor) { + if (sor.ok()) { + g(sor); + } + } + +In such cases it is best to either pass the value directly when calling +a function or check that the status of the ``StatusOr<T>`` is ok in the +local scope of the callee. For example: + +.. code:: cpp + + void g(int val) { + use(val); + } + + void f(absl::StatusOr<int> sor) { + if (sor.ok()) { + g(*sor); + } + } + +Aliases created via ``using`` declarations +------------------------------------------ + +The check is aware of aliases of ``StatusOr<T>`` types that are created +via ``using`` declarations. For example: + +.. code:: cpp + + using StatusOrInt = absl::StatusOr<int>; + + void f(StatusOrInt sor) { + use(*sor); // unsafe: it is unclear whether the status of `sor` is ok. + } + +Containers +---------- + +The check is more strict than necessary when it comes to containers of +``StatusOr<T>`` values. Simply checking that the status of an element of +a container is ok is not sufficient to deem accessing it safe. For +example: + +.. code:: cpp + + void f(std::vector<absl::StatusOr<int>> sors) { + if (sors[0].ok()) { + use(*sors[0]); // unsafe: it is unclear whether the status of `sors[0]` is ok. + } + } + +One needs to grab a reference to a particular object and use that +instead: + +.. code:: cpp + + void f(std::vector<absl::StatusOr<int>> sors) { + absl::StatusOr<int>& sor0 = sors[0]; + if (sor0.ok()) { + use(*sor0); + } + } + +A future version could improve the understanding of more safe usage +patterns that involve containers. + +Lambdas +------- + +The check is capable of reporting unsafe ``StatusOr<T>`` accesses in +lambdas, but isn’t smart enough to propagate information from the +surrounding context through the lambda. This means that the following +pattern will be reported as an unsafe access: + +.. code:: cpp + + void f(absl::StatusOr<int> sor) { + if (sor.ok()) { + [&sor]() { + use(*sor); // unsafe: it is unclear whether the status of `sor` is ok. + } + } + } + +To avoid the issue, you should grab a reference to the contained object +and capture that instead + +.. code:: cpp + + void f(absl::StatusOr<int> sor) { + if (sor.ok()) { + auto& s = *sor; + [&s]() { + use(s); + } + } + } + +Alternatively you could add a check inside the lambda where the value is +accessed: + +.. code:: cpp + + void f(absl::StatusOr<int> sor) { + [&sor]() { + if (sor.ok()) { + use(*sor); + } + } + } >From 77c75b452a8b5ad54009d3fa01cc7863d102dde1 Mon Sep 17 00:00:00 2001 From: Florian Mayer <[email protected]> Date: Mon, 8 Dec 2025 18:24:56 -0800 Subject: [PATCH 4/4] release notes Created using spr 1.3.7 --- clang-tools-extra/docs/ReleaseNotes.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index d1fb1cba3e45a..50b46dc15db58 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -199,6 +199,11 @@ Improvements to clang-tidy New checks ^^^^^^^^^^ +- New :doc:`abseil-unchecked-statusor-access + <clang-tidy/checks/abseil/unchecked-statusor-access>` check. + + Finds uses of ``absl::StatusOr`` without checking if a value is present. + - New :doc:`bugprone-derived-method-shadowing-base-method <clang-tidy/checks/bugprone/derived-method-shadowing-base-method>` check. _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
