diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake index 6371298..66a1747 100644 --- a/CMake/AbseilDll.cmake +++ b/CMake/AbseilDll.cmake
@@ -20,7 +20,6 @@ "base/internal/dynamic_annotations.h" "base/internal/endian.h" "base/internal/errno_saver.h" - "base/internal/hardening.cc" "base/internal/hardening.h" "base/internal/hide_ptr.h" "base/internal/iterator_traits.h" @@ -287,6 +286,9 @@ "status/internal/statusor_internal.h" "status/status.cc" "status/status.h" + "status/status_builder.cc" + "status/status_builder.h" + "status/status_macros.h" "status/status_payload_printer.cc" "status/status_payload_printer.h" "status/statusor.cc"
diff --git a/MODULE.bazel b/MODULE.bazel index edb49d0..cc60d22 100644 --- a/MODULE.bazel +++ b/MODULE.bazel
@@ -17,7 +17,6 @@ module( name = "abseil-cpp", version = "head", - compatibility_level = 1, ) cc_configure = use_extension("@rules_cc//cc:extensions.bzl", @@ -25,9 +24,9 @@ dev_dependency = True) use_repo(cc_configure, "local_config_cc") -bazel_dep(name = "rules_cc", version = "0.2.9") -bazel_dep(name = "bazel_skylib", version = "1.8.1") -bazel_dep(name = "platforms", version = "1.0.0") +bazel_dep(name = "rules_cc", version = "0.2.18") +bazel_dep(name = "bazel_skylib", version = "1.9.0") +bazel_dep(name = "platforms", version = "1.1.0") bazel_dep( name = "google_benchmark",
diff --git a/absl/algorithm/BUILD.bazel b/absl/algorithm/BUILD.bazel index 7d8350c..cdb0af1 100644 --- a/absl/algorithm/BUILD.bazel +++ b/absl/algorithm/BUILD.bazel
@@ -41,6 +41,7 @@ linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ "//absl/base:config", + "//absl/base:core_headers", ], ) @@ -69,6 +70,8 @@ ":algorithm", "//absl/base:config", "//absl/base:core_headers", + "//absl/base:hardening", + "//absl/base:iterator_traits_internal", "//absl/meta:type_traits", ], ) @@ -84,6 +87,7 @@ "//absl/base:config", "//absl/base:core_headers", "//absl/memory", + "//absl/meta:type_traits", "//absl/random", "//absl/types:span", "@googletest//:gtest",
diff --git a/absl/algorithm/CMakeLists.txt b/absl/algorithm/CMakeLists.txt index cdd5d14..cd7fa22 100644 --- a/absl/algorithm/CMakeLists.txt +++ b/absl/algorithm/CMakeLists.txt
@@ -23,6 +23,7 @@ ${ABSL_DEFAULT_COPTS} DEPS absl::config + absl::core_headers PUBLIC ) @@ -48,7 +49,9 @@ ${ABSL_DEFAULT_COPTS} DEPS absl::algorithm + absl::hardening absl::config + absl::iterator_traits_internal absl::core_headers absl::meta PUBLIC @@ -66,6 +69,7 @@ absl::config absl::core_headers absl::memory + absl::meta absl::random_random absl::span GTest::gmock_main
diff --git a/absl/algorithm/algorithm.h b/absl/algorithm/algorithm.h index 48f5950..4e2ebf4 100644 --- a/absl/algorithm/algorithm.h +++ b/absl/algorithm/algorithm.h
@@ -27,6 +27,7 @@ #include <type_traits> #include "absl/base/config.h" +#include "absl/base/macros.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -40,8 +41,39 @@ // // See the documentation for the STL <algorithm> header for more information: // https://en.cppreference.com/w/cpp/header/algorithm -using std::equal; -using std::rotate; + +template <class InputIt1, class InputIt2> +ABSL_DEPRECATE_AND_INLINE() +constexpr bool equal(InputIt1 first1, InputIt1 last1, InputIt2 first2) { + return std::equal(first1, last1, first2); +} + +template <class InputIt1, class InputIt2, class BinaryPredicate> +ABSL_DEPRECATE_AND_INLINE() +constexpr bool equal(InputIt1 first1, InputIt1 last1, InputIt2 first2, + BinaryPredicate p) { + return std::equal(first1, last1, first2, p); +} + +template <class InputIt1, class InputIt2> +ABSL_DEPRECATE_AND_INLINE() +constexpr bool equal(InputIt1 first1, InputIt1 last1, InputIt2 first2, + InputIt2 last2) { + return std::equal(first1, last1, first2, last2); +} + +template <class InputIt1, class InputIt2, class BinaryPredicate> +ABSL_DEPRECATE_AND_INLINE() +constexpr bool equal(InputIt1 first1, InputIt1 last1, InputIt2 first2, + InputIt2 last2, BinaryPredicate p) { + return std::equal(first1, last1, first2, last2, p); +} + +template <class ForwardIt> +ABSL_DEPRECATE_AND_INLINE() +constexpr ForwardIt rotate(ForwardIt first, ForwardIt n_first, ForwardIt last) { + return std::rotate(first, n_first, last); +} // linear_search() //
diff --git a/absl/algorithm/container.h b/absl/algorithm/container.h index a823ee1..965c545 100644 --- a/absl/algorithm/container.h +++ b/absl/algorithm/container.h
@@ -52,6 +52,8 @@ #include "absl/algorithm/algorithm.h" #include "absl/base/config.h" +#include "absl/base/internal/hardening.h" +#include "absl/base/internal/iterator_traits.h" #include "absl/base/macros.h" #include "absl/meta/type_traits.h" @@ -106,6 +108,42 @@ return end(c); } +// Helper to check that the `OutputRange` has enough space. +// Only performs the check if the iterators are ForwardIterators or better. +template <typename InputSequence, typename Size, typename OutputRange> +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX17 void AssertCopyNSize(InputSequence& input, + Size n, + OutputRange& output) { + using InputIter = ContainerIter<InputSequence>; + using OutputIter = ContainerIter<OutputRange>; + + if constexpr (base_internal::IsAtLeastForwardIterator<InputIter>::value) { + base_internal::HardeningAssert( + n <= std::distance(container_algorithm_internal::c_begin(input), + container_algorithm_internal::c_end(input))); + } + if constexpr (base_internal::IsAtLeastForwardIterator<OutputIter>::value) { + base_internal::HardeningAssert( + n <= std::distance(container_algorithm_internal::c_begin(output), + container_algorithm_internal::c_end(output))); + } +} + +template <typename InputSequence, typename OutputRange> +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX17 void AssertCopySize(InputSequence& input, + OutputRange& output) { + using InputIter = ContainerIter<InputSequence>; + using OutputIter = ContainerIter<OutputRange>; + if constexpr (base_internal::IsAtLeastForwardIterator<InputIter>::value && + base_internal::IsAtLeastForwardIterator<OutputIter>::value) { + base_internal::HardeningAssert( + std::distance(container_algorithm_internal::c_begin(input), + container_algorithm_internal::c_end(input)) <= + std::distance(container_algorithm_internal::c_begin(output), + container_algorithm_internal::c_end(output))); + } +} + template <typename T> struct IsUnorderedContainer : std::false_type {}; @@ -117,6 +155,27 @@ struct IsUnorderedContainer<std::unordered_set<Key, Hash, KeyEqual, Allocator>> : std::true_type {}; +template <typename T, typename = void> +struct HasBeginEnd : std::false_type {}; + +template <typename T> +struct HasBeginEnd<T, std::void_t<decltype(container_algorithm_internal::begin( + std::declval<T (*)()>()())), + decltype(container_algorithm_internal::end( + std::declval<T (*)()>()()))>> + : std::true_type {}; + +// We don't support multidimensional arrays yet +template <class T> +using IsMultidimensionalArray = std::is_array<std::remove_extent_t<T>>; + +template <typename Iter, typename = void> +struct IsIterator : std::false_type {}; + +template <typename Iter> +struct IsIterator< + Iter, std::void_t<typename std::iterator_traits<Iter>::iterator_category>> + : std::true_type {}; } // namespace container_algorithm_internal // PUBLIC API @@ -521,10 +580,41 @@ // Container-based version of the <algorithm> `std::copy()` function to copy a // container's elements into an iterator. template <typename InputSequence, typename OutputIterator> -ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 OutputIterator -c_copy(const InputSequence& input, OutputIterator output) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 + std::enable_if_t<container_algorithm_internal::IsIterator< + absl::remove_cvref_t<OutputIterator>>::value && + !container_algorithm_internal::IsMultidimensionalArray< + InputSequence>::value, + std::decay_t<OutputIterator>> + c_copy(const InputSequence& input, OutputIterator&& output) { return std::copy(container_algorithm_internal::c_begin(input), - container_algorithm_internal::c_end(input), output); + container_algorithm_internal::c_end(input), + std::forward<OutputIterator>(output)); +} + +// Copies elements from `input` to `output`. `absl::c_copy(input, output)` is +// equivalent to `std::copy(std::begin(input), std::end(input), +// std::begin(output))`. +// +// The `output` container must be large enough to hold all elements of `input`; +// this function does not resize `output`. + +// If `std::size(input) > std::size(output)`, behavior is undefined. +// If `std::size(output) > std::size(input)`, only `std::size(input)` elements +// are copied, and `output` is not truncated. +template <typename InputSequence, typename OutputRange> +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 + std::enable_if_t<container_algorithm_internal::HasBeginEnd< + std::add_lvalue_reference_t<OutputRange>>::value && + !container_algorithm_internal::IsMultidimensionalArray< + std::remove_reference_t<OutputRange>>::value && + !container_algorithm_internal::IsMultidimensionalArray< + InputSequence>::value, + void> + c_copy(const InputSequence& input, OutputRange&& output) { + container_algorithm_internal::AssertCopySize(input, output); + absl::c_copy(input, container_algorithm_internal::c_begin( + std::forward<OutputRange>(output))); } // c_copy_n() @@ -532,9 +622,40 @@ // Container-based version of the <algorithm> `std::copy_n()` function to copy a // container's first N elements into an iterator. template <typename C, typename Size, typename OutputIterator> -ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 OutputIterator -c_copy_n(const C& input, Size n, OutputIterator output) { - return std::copy_n(container_algorithm_internal::c_begin(input), n, output); +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 std::enable_if_t< + container_algorithm_internal::IsIterator< + absl::remove_cvref_t<OutputIterator>>::value && + !container_algorithm_internal::IsMultidimensionalArray<C>::value, + std::decay_t<OutputIterator>> +c_copy_n(const C& input, Size n, OutputIterator&& output) { + return std::copy_n(container_algorithm_internal::c_begin(input), n, + std::forward<OutputIterator>(output)); +} + +// Copies the first `n` elements from `input` to `output`. +// `absl::c_copy_n(input, n, output)` is equivalent to +// `std::copy_n(std::begin(input), n, std::begin(output))`. +// +// The `output` container must be large enough to hold N elements; this function +// does not resize `output`. +// +// If `n > std::size(output)` or `n > std::size(input)`, behavior is +// undefined. +// If `std::size(output) > n`, only `n` elements are copied, and `output` is not +// truncated. +template <typename C, typename Size, typename OutputRange> +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 std::enable_if_t< + container_algorithm_internal::HasBeginEnd< + std::add_lvalue_reference_t<OutputRange>>::value && + !container_algorithm_internal::IsMultidimensionalArray< + std::remove_reference_t<OutputRange>>::value && + !container_algorithm_internal::IsMultidimensionalArray<C>::value, + void> +c_copy_n(const C& input, Size n, OutputRange&& output) { + container_algorithm_internal::AssertCopyNSize(input, n, output); + absl::c_copy_n( + input, n, + container_algorithm_internal::c_begin(std::forward<OutputRange>(output))); } // c_copy_if() @@ -819,8 +940,8 @@ typename Iterator = container_algorithm_internal::ContainerIter<C>> ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 Iterator c_rotate(C& sequence, Iterator middle) { - return std::rotate(container_algorithm_internal::c_begin(sequence), middle, - container_algorithm_internal::c_end(sequence)); + return absl::rotate(container_algorithm_internal::c_begin(sequence), middle, + container_algorithm_internal::c_end(sequence)); } // c_rotate_copy()
diff --git a/absl/algorithm/container_test.cc b/absl/algorithm/container_test.cc index 8a898be..fcb8492 100644 --- a/absl/algorithm/container_test.cc +++ b/absl/algorithm/container_test.cc
@@ -17,6 +17,7 @@ #include <algorithm> #include <array> #include <cstddef> +#include <forward_list> #include <functional> #include <initializer_list> #include <iterator> @@ -25,6 +26,7 @@ #include <ostream> #include <random> #include <set> +#include <type_traits> #include <unordered_set> #include <utility> #include <valarray> @@ -36,6 +38,7 @@ #include "absl/base/config.h" #include "absl/base/macros.h" #include "absl/memory/memory.h" +#include "absl/meta/type_traits.h" #include "absl/random/random.h" #include "absl/types/span.h" @@ -712,6 +715,190 @@ EXPECT_EQ(expected, actual); } +TEST(MutatingTest, CopyNWithNegativeN) { +#ifdef _LIBCPP_VERSION + GTEST_SKIP() << "libc++ does not handle negative counts correctly"; +#else + const std::vector<int> input = {1, 2, 3}; + std::vector<int> actual = {0, 0, 0}; + absl::c_copy_n(input, -1, actual.begin()); + EXPECT_THAT(actual, ElementsAre(0, 0, 0)); +#endif +} + +TEST(MutatingTest, CopyToContainer) { + const std::vector<int> input = {1, 2, 3}; + std::vector<int> actual = {0, 0, 0, 4, 5}; + absl::c_copy(input, actual); + EXPECT_THAT(actual, ElementsAre(1, 2, 3, 4, 5)); +} + +TEST(MutatingTest, CopyNToContainer) { + const std::vector<int> input = {1, 2, 3, 4, 5}; + std::vector<int> actual = {0, 0, 0, 0, 0}; + absl::c_copy_n(input, 2, actual); + EXPECT_THAT(actual, ElementsAre(1, 2, 0, 0, 0)); +} + +TEST(MutatingTest, CopyNToContainerWithZeroN) { + const std::vector<int> input = {1, 2, 3, 4, 5}; + std::vector<int> actual = {0, 0, 0, 0, 0}; + absl::c_copy_n(input, 0, actual); + EXPECT_THAT(actual, ElementsAre(0, 0, 0, 0, 0)); +} + +TEST(MutatingTest, CopyNToContainerWithNegativeN) { +#ifdef _LIBCPP_VERSION + GTEST_SKIP() << "libc++ does not handle negative counts correctly"; +#else + const std::vector<int> input = {1, 2, 3}; + std::vector<int> actual = {0, 0, 0}; + absl::c_copy_n(input, -1, actual); + EXPECT_THAT(actual, ElementsAre(0, 0, 0)); +#endif +} + +TEST(MutatingTest, CopyToDifferentContainerType) { + const std::list<int> input = {1, 2, 3}; + std::array<int, 5> actual = {0, 0, 0, 4, 5}; + absl::c_copy(input, actual); + EXPECT_THAT(actual, ElementsAre(1, 2, 3, 4, 5)); +} + +TEST(MutatingTest, CopyNToDifferentContainerType) { + const std::list<int> input = {1, 2, 3, 4, 5}; + std::array<int, 5> actual = {0, 0, 0, 0, 0}; + absl::c_copy_n(input, 2, actual); + EXPECT_THAT(actual, ElementsAre(1, 2, 0, 0, 0)); +} + +TEST(MutatingTest, CopyToCArray) { + const std::vector<int> input = {1, 2, 3}; + int actual[5] = {0, 0, 0, 4, 5}; + absl::c_copy(input, actual); + EXPECT_THAT(actual, ElementsAre(1, 2, 3, 4, 5)); +} + +TEST(MutatingTest, CopyNToCArray) { + const std::vector<int> input = {1, 2, 3, 4, 5}; + int actual[5] = {0, 0, 0, 0, 0}; + absl::c_copy_n(input, 2, actual); + EXPECT_THAT(actual, ElementsAre(1, 2, 0, 0, 0)); +} + +TEST(MutatingTest, CopyFromCArray) { + const int input[5] = {1, 2, 3, 4, 5}; + std::vector<int> actual = {0, 0, 0, 0, 0}; + absl::c_copy(input, actual); + EXPECT_THAT(actual, ElementsAre(1, 2, 3, 4, 5)); +} + +TEST(MutatingTest, CopyNFromCArray) { + const int input[5] = {1, 2, 3, 4, 5}; + std::vector<int> actual = {0, 0, 0, 0, 0}; + absl::c_copy_n(input, 2, actual); + EXPECT_THAT(actual, ElementsAre(1, 2, 0, 0, 0)); +} + +TEST(MutatingTest, CopyContainerWithNoSizeMethod) { + const std::forward_list<int> input = {1, 2, 3}; + std::forward_list<int> actual = {0, 0, 0, 4, 5}; + absl::c_copy(input, actual); + EXPECT_THAT(actual, ElementsAre(1, 2, 3, 4, 5)); +} + +TEST(MutatingTest, CopyNContainerWithNoSizeMethod) { + const std::forward_list<int> input = {1, 2, 3, 4, 5}; + std::forward_list<int> actual = {0, 0, 0, 0, 0}; + absl::c_copy_n(input, 2, actual); + EXPECT_THAT(actual, ElementsAre(1, 2, 0, 0, 0)); +} + +#if GTEST_HAS_DEATH_TEST + +bool IsHardened() { + bool hardened = false; + ABSL_HARDENING_ASSERT([&hardened]() { + hardened = true; + return true; + }()); + return hardened; +} + +TEST(MutatingTest, CopyToCArrayInvalidSize) { + const std::vector<int> input = {1, 2, 3}; + int actual[2] = {0, 0}; + if (IsHardened()) { + EXPECT_DEATH(absl::c_copy(input, actual), ""); + } +} + +TEST(MutatingTest, CopyNToCArrayInvalidSize) { + const std::vector<int> input = {1, 2, 3}; + int actual[2] = {0, 0}; + if (IsHardened()) { + EXPECT_DEATH(absl::c_copy_n(input, 3, actual), ""); + } +} + +TEST(MutatingTest, CopyNToCArrayNGreaterThanInput) { + const std::vector<int> input = {1, 2, 3}; + int actual[4] = {0, 0, 0, 0}; + if (IsHardened()) { + EXPECT_DEATH(absl::c_copy_n(input, 4, actual), ""); + } +} + +TEST(MutatingTest, CopyToContainerInvalidSize) { + const std::list<int> input = {1, 2, 3, 4, 5}; + std::list<int> actual = {0, 0, 0}; + if (IsHardened()) { + EXPECT_DEATH(absl::c_copy(input, actual), ""); + } +} + +TEST(MutatingTest, CopyNToContainerNGreaterThanInput) { + const std::vector<int> input = {1, 2, 3}; + std::vector<int> actual = {0, 0, 0, 0}; + if (IsHardened()) { + EXPECT_DEATH(absl::c_copy_n(input, 4, actual), ""); + } +} + +TEST(MutatingTest, CopyNToContainerNGreaterThanOutput) { + const std::vector<int> input = {1, 2, 3}; + std::vector<int> actual = {0, 0}; + if (IsHardened()) { + EXPECT_DEATH(absl::c_copy_n(input, 3, actual), ""); + } +} + +TEST(MutatingTest, CopyToForwardListInvalidSize) { + const std::forward_list<int> input = {1, 2, 3, 4, 5}; + std::forward_list<int> actual = {0, 0, 0}; + if (IsHardened()) { + EXPECT_DEATH(absl::c_copy(input, actual), ""); + } +} + +TEST(MutatingTest, CopyNToForwardListNGreaterThanInput) { + const std::forward_list<int> input = {1, 2, 3}; + std::forward_list<int> actual = {0, 0, 0, 0}; + if (IsHardened()) { + EXPECT_DEATH(absl::c_copy_n(input, 4, actual), ""); + } +} + +TEST(MutatingTest, CopyNToForwardListNGreaterThanOutput) { + const std::forward_list<int> input = {1, 2, 3}; + std::forward_list<int> actual = {0, 0}; + if (IsHardened()) { + EXPECT_DEATH(absl::c_copy_n(input, 3, actual), ""); + } +} + +#endif // GTEST_HAS_DEATH_TEST + TEST(MutatingTest, CopyIf) { const std::list<int> input = {1, 2, 3}; std::vector<int> output; @@ -2229,4 +2416,79 @@ #endif // defined(ABSL_INTERNAL_CPLUSPLUS_LANG) && // ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L +// A type that acts as both a container and an iterator. +struct AmbiguousType { + // Container requirements + int* begin() { return nullptr; } + int* end() { return nullptr; } + + // Iterator requirements + using iterator_category = std::input_iterator_tag; + using value_type = int; + using difference_type = std::ptrdiff_t; + using pointer = int*; + using reference = int&; + + int& operator*() { + static int x; + return x; + } + AmbiguousType& operator++() { return *this; } + AmbiguousType operator++(int) { return *this; } + friend bool operator==(const AmbiguousType&, const AmbiguousType&) { + return true; + } + friend bool operator!=(const AmbiguousType&, const AmbiguousType&) { + return false; + } +}; + +template <typename Container, typename Output, typename = void> +struct CanCopy : std::false_type {}; +template <typename Container, typename Output> +struct CanCopy<Container, Output, + absl::void_t<decltype(absl::c_copy(std::declval<Container>(), + std::declval<Output>()))>> + : std::true_type {}; + +template <typename Container, typename Output, typename = void> +struct CanCopyN : std::false_type {}; +template <typename Container, typename Output> +struct CanCopyN<Container, Output, + absl::void_t<decltype(absl::c_copy_n( + std::declval<Container>(), std::declval<ptrdiff_t>(), + std::declval<Output>()))>> : std::true_type {}; + +TEST(CanCopyTest, CopyToMultiDimArray) { + static_assert(CanCopy<std::vector<int>, int (&)[10]>::value); + static_assert(!CanCopy<std::vector<int>, int (&)[2][2]>::value); + static_assert(CanCopyN<std::vector<int>, int (&)[10]>::value); + static_assert(!CanCopyN<std::vector<int>, int (&)[2][2]>::value); + + static_assert(CanCopy<int[10], int (&)[10]>::value); + static_assert(!CanCopy<int[10], int (&)[2][2]>::value); + static_assert(CanCopyN<int[10], int (&)[10]>::value); + static_assert(!CanCopyN<int[10], int (&)[2][2]>::value); + static_assert(!CanCopy<int[2][2], int (&)[4]>::value); + static_assert(!CanCopy<int[2][2], int (&)[2][2]>::value); + static_assert(!CanCopyN<int[2][2], int (&)[4]>::value); + static_assert(!CanCopyN<int[2][2], int (&)[2][2]>::value); +} + +TEST(CanCopyTest, BlockNonWritableIterators) { + using Vec = std::vector<int>; + + static_assert(CanCopy<Vec, Vec::iterator>::value); + static_assert(CanCopy<Vec, std::back_insert_iterator<Vec>>::value); +} + +TEST(CanCopyTest, AmbiguousTypeFailsToCompile) { + using Vec = std::vector<int>; + // Because AmbiguousType is both an iterator and a container, + // the compiler should fail to resolve the c_copy overload. + static_assert(!CanCopy<Vec, AmbiguousType>::value, + "Ambiguous types should not compile!"); + static_assert(!CanCopyN<Vec, AmbiguousType>::value, + "Ambiguous types should not compile!"); +} } // namespace
diff --git a/absl/base/BUILD.bazel b/absl/base/BUILD.bazel index 98772b1..6ade148 100644 --- a/absl/base/BUILD.bazel +++ b/absl/base/BUILD.bazel
@@ -63,9 +63,6 @@ cc_library( name = "hardening", - srcs = [ - "internal/hardening.cc", - ], hdrs = [ "internal/hardening.h", ],
diff --git a/absl/base/CMakeLists.txt b/absl/base/CMakeLists.txt index 582b14c..9608061 100644 --- a/absl/base/CMakeLists.txt +++ b/absl/base/CMakeLists.txt
@@ -46,8 +46,6 @@ hardening HDRS "internal/hardening.h" - SRCS - "internal/hardening.cc" DEPS absl::config absl::core_headers
diff --git a/absl/base/internal/hardening.cc b/absl/base/internal/hardening.cc deleted file mode 100644 index 2844eec..0000000 --- a/absl/base/internal/hardening.cc +++ /dev/null
@@ -1,46 +0,0 @@ -// -// Copyright 2026 The Abseil Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifdef _WIN32 -#include <intrin.h> -// kFastFailInvalidArg mirrors the FAST_FAIL_INVALID_ARG macro. -// Typically FAST_FAIL_INVALID_ARG would be imported from winnt.h -// but winnt.h pulls in other dependencies and introduces build failures. -constexpr unsigned int kFastFailInvalidArg = 5u; -#endif - -#include "absl/base/internal/hardening.h" - -#include "absl/base/attributes.h" -#include "absl/base/config.h" -#include "absl/base/macros.h" - -namespace absl { -ABSL_NAMESPACE_BEGIN - -namespace base_internal { - -[[noreturn]] ABSL_ATTRIBUTE_NOINLINE void HardeningAbort() { -#ifdef _WIN32 - __fastfail(kFastFailInvalidArg); -#else - ABSL_INTERNAL_HARDENING_ABORT(); -#endif -} - -} // namespace base_internal - -ABSL_NAMESPACE_END -} // namespace absl
diff --git a/absl/base/internal/hardening.h b/absl/base/internal/hardening.h index 6ea2833..31b25d9 100644 --- a/absl/base/internal/hardening.h +++ b/absl/base/internal/hardening.h
@@ -23,26 +23,15 @@ #ifndef ABSL_BASE_INTERNAL_HARDENING_H_ #define ABSL_BASE_INTERNAL_HARDENING_H_ -#include "absl/base/attributes.h" #include "absl/base/config.h" #include "absl/base/macros.h" #include "absl/base/options.h" -#ifdef ABSL_INTERNAL_ATTRIBUTE_NO_MERGE -#error ABSL_INTERNAL_ATTRIBUTE_NO_MERGE cannot be directly set -#elif ABSL_HAVE_CPP_ATTRIBUTE(clang::nomerge) -#define ABSL_INTERNAL_ATTRIBUTE_NO_MERGE [[clang::nomerge]] -#else -#define ABSL_INTERNAL_ATTRIBUTE_NO_MERGE -#endif - namespace absl { ABSL_NAMESPACE_BEGIN namespace base_internal { -[[noreturn]] ABSL_ATTRIBUTE_NOINLINE void HardeningAbort(); - // `HardeningAssert` performs runtime checks when Abseil Hardening is enabled, // even if `NDEBUG` is defined. // @@ -56,7 +45,7 @@ ABSL_ASSERT(cond); #if (ABSL_OPTION_HARDENED == 1 || ABSL_OPTION_HARDENED == 2) && defined(NDEBUG) if (ABSL_PREDICT_FALSE(!cond)) { - ABSL_INTERNAL_ATTRIBUTE_NO_MERGE HardeningAbort(); + base_internal::HardeningAbort(); } #endif } @@ -70,7 +59,7 @@ ABSL_ASSERT(cond); #if (ABSL_OPTION_HARDENED == 1) && defined(NDEBUG) if (ABSL_PREDICT_FALSE(!cond)) { - ABSL_INTERNAL_ATTRIBUTE_NO_MERGE HardeningAbort(); + base_internal::HardeningAbort(); } #endif } @@ -80,7 +69,7 @@ ABSL_ASSERT(val1 > val2); #if (ABSL_OPTION_HARDENED == 1 || ABSL_OPTION_HARDENED == 2) && defined(NDEBUG) if (!ABSL_PREDICT_TRUE(val1 > val2)) { - ABSL_INTERNAL_ATTRIBUTE_NO_MERGE HardeningAbort(); + base_internal::HardeningAbort(); } #endif } @@ -90,7 +79,7 @@ ABSL_ASSERT(val1 >= val2); #if (ABSL_OPTION_HARDENED == 1 || ABSL_OPTION_HARDENED == 2) && defined(NDEBUG) if (!ABSL_PREDICT_TRUE(val1 >= val2)) { - ABSL_INTERNAL_ATTRIBUTE_NO_MERGE HardeningAbort(); + base_internal::HardeningAbort(); } #endif } @@ -100,7 +89,7 @@ ABSL_ASSERT(val1 < val2); #if (ABSL_OPTION_HARDENED == 1 || ABSL_OPTION_HARDENED == 2) && defined(NDEBUG) if (!ABSL_PREDICT_TRUE(val1 < val2)) { - ABSL_INTERNAL_ATTRIBUTE_NO_MERGE HardeningAbort(); + base_internal::HardeningAbort(); } #endif } @@ -110,7 +99,7 @@ ABSL_ASSERT(val1 <= val2); #if (ABSL_OPTION_HARDENED == 1 || ABSL_OPTION_HARDENED == 2) && defined(NDEBUG) if (!ABSL_PREDICT_TRUE(val1 <= val2)) { - ABSL_INTERNAL_ATTRIBUTE_NO_MERGE HardeningAbort(); + base_internal::HardeningAbort(); } #endif } @@ -124,7 +113,7 @@ ABSL_ASSERT(!container.empty()); #if (ABSL_OPTION_HARDENED == 1 || ABSL_OPTION_HARDENED == 2) && defined(NDEBUG) if (ABSL_PREDICT_FALSE(container.empty())) { - ABSL_INTERNAL_ATTRIBUTE_NO_MERGE HardeningAbort(); + base_internal::HardeningAbort(); } #endif } @@ -134,7 +123,7 @@ ABSL_ASSERT(ptr != nullptr); #if (ABSL_OPTION_HARDENED == 1 || ABSL_OPTION_HARDENED == 2) && defined(NDEBUG) if (ABSL_PREDICT_FALSE(ptr == nullptr)) { - ABSL_INTERNAL_ATTRIBUTE_NO_MERGE HardeningAbort(); + base_internal::HardeningAbort(); } #endif } @@ -144,6 +133,4 @@ ABSL_NAMESPACE_END } // namespace absl -#undef ABSL_INTERNAL_ATTRIBUTE_NO_MERGE - #endif // ABSL_BASE_INTERNAL_HARDENING_H_
diff --git a/absl/base/macros.h b/absl/base/macros.h index 392b7b8..8c4a34d 100644 --- a/absl/base/macros.h +++ b/absl/base/macros.h
@@ -53,6 +53,19 @@ template <typename T, size_t N> auto ArraySizeHelper(const T (&array)[N]) -> char (&)[N]; } // namespace macros_internal + +namespace base_internal { +#if ABSL_HAVE_CPP_ATTRIBUTE(clang::nomerge) +[[clang::nomerge]] // Needed when this function is not inlined +#endif +[[noreturn]] inline void HardeningAbort() { +#if ABSL_HAVE_CPP_ATTRIBUTE(clang::nomerge) + [[clang::nomerge]] // Needed when this function is inlined +#endif + ABSL_INTERNAL_IMMEDIATE_ABORT_IMPL(); + ABSL_INTERNAL_UNREACHABLE_IMPL(); +} +} // namespace base_internal ABSL_NAMESPACE_END } // namespace absl @@ -122,8 +135,7 @@ // aborts the program in release mode (when NDEBUG is defined). The // implementation should abort the program as quickly as possible and ideally it // should not be possible to ignore the abort request. -#define ABSL_INTERNAL_HARDENING_ABORT() \ - ((void)ABSL_INTERNAL_IMMEDIATE_ABORT_IMPL(), ABSL_INTERNAL_UNREACHABLE_IMPL()) +#define ABSL_INTERNAL_HARDENING_ABORT() ::absl::base_internal::HardeningAbort() // ABSL_HARDENING_ASSERT() //
diff --git a/absl/base/optimization.h b/absl/base/optimization.h index 1fd32c5..9b52a92 100644 --- a/absl/base/optimization.h +++ b/absl/base/optimization.h
@@ -31,6 +31,7 @@ #define ABSL_BASE_OPTIMIZATION_H_ #include <assert.h> +#include <stdlib.h> #ifdef __cplusplus // Included for std::unreachable()
diff --git a/absl/base/policy_checks.h b/absl/base/policy_checks.h index f84944c..88b5774 100644 --- a/absl/base/policy_checks.h +++ b/absl/base/policy_checks.h
@@ -46,17 +46,17 @@ // Toolchain Check // ----------------------------------------------------------------------------- -// We support Visual Studio 2019 (MSVC++ 16.0) and later. +// We support Visual Studio 2022 (MSVC++ 17.0) and later. // This minimum will go up. -#if defined(_MSC_VER) && _MSC_VER < 1920 && !defined(__clang__) -#error "This package requires Visual Studio 2019 (MSVC++ 16.0) or higher." +#if defined(_MSC_VER) && _MSC_VER < 1930 && !defined(__clang__) +#error "This package requires Visual Studio 2022 (MSVC++ 17.0) or higher." #endif // We support GCC 7 and later. // This minimum will go up. #if defined(__GNUC__) && !defined(__clang__) -#if __GNUC__ < 7 -#error "This package requires GCC 7 or higher." +#if __GNUC__ < 10 +#error "This package requires GCC 10 or higher." #endif #endif
diff --git a/absl/container/internal/container_memory.h b/absl/container/internal/container_memory.h index fc7633f..16a2e25 100644 --- a/absl/container/internal/container_memory.h +++ b/absl/container/internal/container_memory.h
@@ -550,10 +550,20 @@ size_t TypeErasedDerefAndApplyToSlotFn(const void* fn, void* slot_ptr, size_t seed) { const auto* f = static_cast<const Fn*>(fn); - const T* slot = *static_cast<const T**>(slot_ptr); + const T* slot = *static_cast<T**>(slot_ptr); return HashElement<Fn, kIsDefault>{*f, seed}(*slot); } +// Type erased function to apply `Fn` to data inside of the `slot_ptr->first`. +// The data is expected to have type `T`. +template <class Fn, class T, bool kIsDefault> +size_t TypeErasedDerefAndApplyToSlotFirstFn(const void* fn, void* slot_ptr, + size_t seed) { + const auto* f = static_cast<const Fn*>(fn); + const T* slot = *static_cast<T**>(slot_ptr); + return HashElement<Fn, kIsDefault>{*f, seed}(slot->first); +} + } // namespace container_internal ABSL_NAMESPACE_END } // namespace absl
diff --git a/absl/container/internal/hashtablez_sampler.cc b/absl/container/internal/hashtablez_sampler.cc index 18c8ca4..c761304 100644 --- a/absl/container/internal/hashtablez_sampler.cc +++ b/absl/container/internal/hashtablez_sampler.cc
@@ -22,6 +22,7 @@ #include <cstdint> #include <functional> #include <limits> +#include <utility> #include "absl/base/attributes.h" #include "absl/base/config.h" @@ -147,7 +148,7 @@ size_t value_size, uint16_t soo_capacity) { if (ABSL_PREDICT_FALSE(ShouldForceSampling())) { next_sample.next_sample = 1; - const int64_t old_stride = exchange(next_sample.sample_stride, 1); + const int64_t old_stride = std::exchange(next_sample.sample_stride, 1); HashtablezInfo* result = GlobalHashtablezSampler().Register( old_stride, inline_element_size, key_size, value_size, soo_capacity); return result;
diff --git a/absl/container/internal/raw_hash_set.cc b/absl/container/internal/raw_hash_set.cc index fa6b982..675e3a3 100644 --- a/absl/container/internal/raw_hash_set.cc +++ b/absl/container/internal/raw_hash_set.cc
@@ -91,8 +91,8 @@ // `min(1, RehashProbabilityConstant() / capacity())`. In order to do this, // we probe based on a random hash and see if the offset is less than // RehashProbabilityConstant(). - return probe(capacity, absl::HashOf(RandomSeed())).offset() < - RehashProbabilityConstant(); + return probe(HashtableCapacity(capacity), absl::HashOf(RandomSeed())) + .offset() < RehashProbabilityConstant(); } // Find a non-deterministic hash for single group table. @@ -185,12 +185,12 @@ } FindInfo find_first_non_full_from_h1(const ctrl_t* ctrl, size_t h1, - size_t capacity) { + HashtableCapacity capacity) { auto seq = probe_h1(capacity, h1); if (IsEmptyOrDeleted(ctrl[seq.offset()])) { return {seq.offset(), /*probe_length=*/0}; } - auto mask = probe_till_first_non_full_group(ctrl, seq, capacity); + auto mask = probe_till_first_non_full_group(ctrl, seq, capacity.capacity()); return {seq.offset(mask.LowestBitSet()), seq.index()}; } @@ -203,7 +203,7 @@ // slots in the same group. Such tables appear during `erase()`. FindInfo find_first_non_full(const CommonFields& common, size_t hash) { return find_first_non_full_from_h1(common.control(), H1(hash), - common.capacity()); + common.capacity_impl()); } // Same as `find_first_non_full`, but returns the mask corresponding to the @@ -869,7 +869,7 @@ const size_t soo_slot_hash = policy.hash_slot(policy.hash_fn(c), c.soo_data(), c.seed().seed()); - size_t offset = probe(new_capacity, soo_slot_hash).offset(); + size_t offset = probe(c.capacity_impl(), soo_slot_hash).offset(); offset = offset == new_capacity ? 0 : offset; SanitizerPoisonMemoryRegion(new_slots, policy.slot_size * new_capacity); void* target_slot = SlotAddress(new_slots, offset, policy.slot_size); @@ -1072,7 +1072,7 @@ ABSL_ATTRIBUTE_NOINLINE size_t DecodeAndInsertImpl( CommonFields& c, const PolicyFunctions& __restrict policy, const ProbedItem* start, const ProbedItem* end, void* old_slots) { - const size_t new_capacity = c.capacity(); + const HashtableCapacity new_capacity = c.capacity_impl(); void* new_slots = c.slot_array(); ctrl_t* new_ctrl = c.control(); @@ -1087,8 +1087,8 @@ total_probe_length += target.probe_length; const size_t old_index = static_cast<size_t>(start->source_offset); const size_t new_i = target.offset; - ABSL_SWISSTABLE_ASSERT(old_index < new_capacity / 2); - ABSL_SWISSTABLE_ASSERT(new_i < new_capacity); + ABSL_SWISSTABLE_ASSERT(old_index < new_capacity.capacity() / 2); + ABSL_SWISSTABLE_ASSERT(new_i < new_capacity.capacity()); ABSL_SWISSTABLE_ASSERT(IsEmpty(new_ctrl[new_i])); void* src_slot = SlotAddress(old_slots, old_index, slot_size); void* dst_slot = SlotAddress(new_slots, new_i, slot_size);
diff --git a/absl/container/internal/raw_hash_set.h b/absl/container/internal/raw_hash_set.h index 4aed15a..191b1c9 100644 --- a/absl/container/internal/raw_hash_set.h +++ b/absl/container/internal/raw_hash_set.h
@@ -225,6 +225,10 @@ #include <ranges> // NOLINT(build/c++20) #endif +#ifdef __BMI2__ +#include <bmi2intrin.h> +#endif // __BMI2__ + namespace absl { ABSL_NAMESPACE_BEGIN namespace container_internal { @@ -301,57 +305,6 @@ template <typename AllocType> void CopyAlloc(AllocType&, AllocType&, std::false_type /* propagate_alloc */) {} -// The state for a probe sequence. -// -// Currently, the sequence is a triangular progression of the form -// -// p(i) := Width * (i^2 + i)/2 + hash (mod mask + 1) -// -// The use of `Width` ensures that each probe step does not overlap groups; -// the sequence effectively outputs the addresses of *groups* (although not -// necessarily aligned to any boundary). The `Group` machinery allows us -// to check an entire group with minimal branching. -// -// Wrapping around at `mask + 1` is important, but not for the obvious reason. -// As described above, the first few entries of the control byte array -// are mirrored at the end of the array, which `Group` will find and use -// for selecting candidates. However, when those candidates' slots are -// actually inspected, there are no corresponding slots for the cloned bytes, -// so we need to make sure we've treated those offsets as "wrapping around". -// -// It turns out that this probe sequence visits every group exactly once if the -// number of groups is a power of two, since (i^2+i)/2 is a bijection in -// Z/(2^m). See https://en.wikipedia.org/wiki/Quadratic_probing -template <size_t Width> -class probe_seq { - public: - // Creates a new probe sequence using `hash` as the initial value of the - // sequence and `mask` (usually the capacity of the table) as the mask to - // apply to each value in the progression. - probe_seq(size_t hash, size_t mask) { - ABSL_SWISSTABLE_ASSERT(((mask + 1) & mask) == 0 && "not a mask"); - mask_ = mask; - offset_ = hash & mask_; - } - - // The offset within the table, i.e., the value `p(i)` above. - size_t offset() const { return offset_; } - size_t offset(size_t i) const { return (offset_ + i) & mask_; } - - void next() { - index_ += Width; - offset_ += index_; - offset_ &= mask_; - } - // 0-based probe index, a multiple of `Width`. - size_t index() const { return index_; } - - private: - size_t mask_; - size_t offset_; - size_t index_ = 0; -}; - template <class ContainerKey, class Hash, class Eq> struct RequireUsableKey { template <class PassedKey, class... Args> @@ -580,6 +533,19 @@ return capacity_data_ <= 1; } + constexpr size_t mask(size_t value) const { +#ifdef __BMI2__ + if constexpr (StorageMode == kCapacityByLog) { + if constexpr (sizeof(size_t) == 8) { + return _bzhi_u64(value, capacity_data_); + } else { + return _bzhi_u32(value, capacity_data_); + } + } +#endif // __BMI2__ + return value & capacity(); + } + private: // We use these sentinel capacity values in debug mode to indicate different // classes of bugs. @@ -667,11 +633,10 @@ data_(kSizeOneNoMetadata | (has_tried_sampling ? kSooHasTriedSamplingMask : 0)) {} - HashtableCapacity maybe_invalid_capacity() const { + HashtableCapacity capacity() const { return HashtableCapacity::FromRawData(capacity_internal_); } - size_t capacity() const { return maybe_invalid_capacity().capacity(); } - bool is_small() const { return maybe_invalid_capacity().is_small(); } + bool is_small() const { return capacity().is_small(); } void set_capacity(HashtableCapacity c) { capacity_internal_ = c.ToRawData(); } void set_capacity(size_t c) { set_capacity(HashtableCapacity(c)); } @@ -1251,10 +1216,16 @@ } void set_no_seed_for_testing() { inline_data_.set_no_seed_for_testing(); } - // The total number of available slots. - size_t capacity() const { return inline_data_.capacity(); } + HashtableCapacity capacity_impl() const { + HashtableCapacity cap = inline_data_.capacity(); + ABSL_SWISSTABLE_ASSERT(cap.IsValid()); + return cap; + } + size_t capacity() const { return capacity_impl().capacity(); } + // We have a separate alias for callsites in which the capacity may be + // invalid. HashtableCapacity maybe_invalid_capacity() const { - return inline_data_.maybe_invalid_capacity(); + return inline_data_.capacity(); } void set_capacity(HashtableCapacity c) { inline_data_.set_capacity(c); } void set_capacity(size_t c) { @@ -1408,6 +1379,26 @@ #endif } +// Dereferences `ptr`. The function is named in order to provide a helpful error +// message when users see crashing stack traces. Note that this function is not +// guaranteed to crash when `ptr` is invalid if sanitizer mode is not enabled. +template <typename T> +T CrashIfIteratorIsInvalid(const T* ptr) { + // If the following line(s) crash, then it's likely that `ptr` is from a + // backing array that has been deallocated. If you see a crash here, it likely + // means that you are comparing an invalid iterator from a table that has + // rehashed, moved, or been destroyed. In such cases, it is often helpful to + // reproduce the issue with --config=asan and (assuming there's a crash here) + // examine the corresponding deallocation stack trace. + T ret = *ptr; + // Force a read with inline asm to make sure that a crash happens here, rather + // than later when the value is used. +#ifdef __clang__ + asm("" : "+r"(ret)); +#endif + return ret; +} + inline void AssertIsFull(const ctrl_t* ctrl, GenerationType generation, const GenerationType* generation_ptr, const char* operation) { @@ -1425,20 +1416,21 @@ operation); } if (SwisstableGenerationsEnabled()) { - if (ABSL_PREDICT_FALSE(generation != *generation_ptr)) { + if (ABSL_PREDICT_FALSE(generation != + CrashIfIteratorIsInvalid(generation_ptr))) { ABSL_RAW_LOG(FATAL, "%s called on invalid iterator. The table could have " "rehashed or moved since this iterator was initialized.", operation); } - if (ABSL_PREDICT_FALSE(!IsFull(*ctrl))) { + if (ABSL_PREDICT_FALSE(!IsFull(CrashIfIteratorIsInvalid(ctrl)))) { ABSL_RAW_LOG( FATAL, "%s called on invalid iterator. The element was likely erased.", operation); } } else { - if (ABSL_PREDICT_FALSE(!IsFull(*ctrl))) { + if (ABSL_PREDICT_FALSE(!IsFull(CrashIfIteratorIsInvalid(ctrl)))) { ABSL_RAW_LOG( FATAL, "%s called on invalid iterator. The element might have been erased " @@ -1454,20 +1446,15 @@ GenerationType generation, const GenerationType* generation_ptr) { if (!SwisstableDebugEnabled()) return; - const bool ctrl_is_valid_for_comparison = [ctrl]() { - if (ctrl == nullptr) return true; - if (ctrl == DefaultIterControl()) return true; - // Note: if the following line crashes, then it's likely that `ctrl` is from - // a backing array that has been deallocated. If you see a crash here, it - // likely means that you are comparing an invalid iterator from a table that - // has rehashed, moved, or been destroyed. - return IsFull(*ctrl); - }(); + const bool ctrl_is_valid_for_comparison = + ctrl == nullptr || ctrl == DefaultIterControl() || + IsFull(CrashIfIteratorIsInvalid(ctrl)); if (SwisstableGenerationsEnabled()) { - if (ABSL_PREDICT_FALSE(generation != *generation_ptr)) { + if (ABSL_PREDICT_FALSE(generation != + CrashIfIteratorIsInvalid(generation_ptr))) { // Note: in the case of a rehash, we would expect to see a sanitizer crash - // above when `ctrl` is dereferenced so this assertion will only catch - // moved table cases, unless we're using a custom allocator that does not + // in CrashIfIteratorIsInvalid so this assertion will only catch moved + // table cases, unless we're using a custom allocator that does not // deallocate the old backing array (e.g. an arena allocator). ABSL_RAW_LOG( FATAL, @@ -1576,15 +1563,64 @@ return capacity <= Group::kWidth; } +// The state for a probe sequence. +// +// Currently, the sequence is a triangular progression of the form +// +// p(i) := Width * (i^2 + i)/2 + hash (mod mask + 1) +// +// The use of `Width` ensures that each probe step does not overlap groups; +// the sequence effectively outputs the addresses of *groups* (although not +// necessarily aligned to any boundary). The `Group` machinery allows us +// to check an entire group with minimal branching. +// +// Wrapping around at `mask + 1` is important, but not for the obvious reason. +// As described above, the first few entries of the control byte array +// are mirrored at the end of the array, which `Group` will find and use +// for selecting candidates. However, when those candidates' slots are +// actually inspected, there are no corresponding slots for the cloned bytes, +// so we need to make sure we've treated those offsets as "wrapping around". +// +// It turns out that this probe sequence visits every group exactly once if the +// number of groups is a power of two, since (i^2+i)/2 is a bijection in +// Z/(2^m). See https://en.wikipedia.org/wiki/Quadratic_probing +template <size_t Width> +class probe_seq { + public: + // Creates a new probe sequence using `hash` as the initial value of the + // sequence and `capacity` as the mask to apply to each value in the + // progression. + probe_seq(HashtableCapacity capacity, size_t hash) + : capacity_(capacity), offset_(capacity.mask(hash)) {} + + // The offset within the table, i.e., the value `p(i)` above. + size_t offset() const { return offset_; } + size_t offset(size_t i) const { return capacity_.mask(offset_ + i); } + + void next() { + index_ += Width; + offset_ += index_; + offset_ = capacity_.mask(offset_); + } + // 0-based probe index, a multiple of `Width`. + size_t index() const { return index_; } + + private: + HashtableCapacity capacity_; + size_t offset_; + size_t index_ = 0; +}; + // Begins a probing operation on `common.control`, using `hash`. -inline probe_seq<Group::kWidth> probe_h1(size_t capacity, size_t h1) { - return probe_seq<Group::kWidth>(h1, capacity); +inline probe_seq<Group::kWidth> probe_h1(HashtableCapacity capacity, + size_t h1) { + return probe_seq<Group::kWidth>(capacity, h1); } -inline probe_seq<Group::kWidth> probe(size_t capacity, size_t hash) { +inline probe_seq<Group::kWidth> probe(HashtableCapacity capacity, size_t hash) { return probe_h1(capacity, H1(hash)); } inline probe_seq<Group::kWidth> probe(const CommonFields& common, size_t hash) { - return probe(common.capacity(), hash); + return probe(common.capacity_impl(), hash); } constexpr size_t kProbedElementIndexSentinel = ~size_t{};
diff --git a/absl/container/internal/raw_hash_set_test.cc b/absl/container/internal/raw_hash_set_test.cc index 0edf6bf..3f81f26 100644 --- a/absl/container/internal/raw_hash_set_test.cc +++ b/absl/container/internal/raw_hash_set_test.cc
@@ -338,7 +338,8 @@ } TEST(Util, probe_seq) { - probe_seq<16> seq(0, 127); + HashtableCapacity capacity(127); + probe_seq<16> seq(capacity, /*hash=*/0); auto gen = [&]() { size_t res = seq.offset(); seq.next(); @@ -347,7 +348,7 @@ std::vector<size_t> offsets(8); std::generate_n(offsets.begin(), 8, gen); EXPECT_THAT(offsets, ElementsAre(0, 16, 48, 96, 32, 112, 80, 64)); - seq = probe_seq<16>(128, 127); + seq = probe_seq<16>(capacity, /*hash=*/128); std::generate_n(offsets.begin(), 8, gen); EXPECT_THAT(offsets, ElementsAre(0, 16, 48, 96, 32, 112, 80, 64)); } @@ -421,24 +422,24 @@ using Capacity = HashtableCapacityImpl<kMode>; InlineData data(Capacity(0), no_seed_empty_tag_t{}); - EXPECT_EQ(data.capacity(), 0); + EXPECT_EQ(data.capacity().capacity(), 0); EXPECT_EQ(data.size(), 0); EXPECT_TRUE(data.empty()); for (size_t i = 0, cap = 0; i < 20; ++i, cap = NextCapacity(cap)) { data.set_capacity(cap); - ASSERT_EQ(data.capacity(), cap); + ASSERT_EQ(data.capacity().capacity(), cap); } // Test overload from `Capacity` object. for (size_t i = 0, cap = 0; i < 20; ++i, cap = NextCapacity(cap)) { data.set_capacity(Capacity(cap)); - ASSERT_EQ(data.capacity(), cap); + ASSERT_EQ(data.capacity().capacity(), cap); } auto reentrance = Capacity::CreateReentrance(); data.set_capacity(reentrance); - EXPECT_TRUE(data.maybe_invalid_capacity().IsReentrance()); + EXPECT_TRUE(data.capacity().IsReentrance()); } TYPED_TEST(HashtableDataTest, HashtableInlineDataSize) { @@ -502,7 +503,7 @@ { InlineData data_soo(Capacity(1), full_soo_tag_t{}, /*has_tried_sampling=*/true); - EXPECT_EQ(data_soo.capacity(), 1); + EXPECT_EQ(data_soo.capacity().capacity(), 1); EXPECT_EQ(data_soo.size(), 1); EXPECT_TRUE(data_soo.soo_has_tried_sampling()); } @@ -510,7 +511,7 @@ { InlineData data_soo(Capacity(1), full_soo_tag_t{}, /*has_tried_sampling=*/false); - EXPECT_EQ(data_soo.capacity(), 1); + EXPECT_EQ(data_soo.capacity().capacity(), 1); EXPECT_EQ(data_soo.size(), 1); EXPECT_FALSE(data_soo.soo_has_tried_sampling()); } @@ -2921,12 +2922,11 @@ // the control is static constant. } -// Invalid iterator use can trigger use-after-free in asan/hwasan, -// use-of-uninitialized-value in msan, or invalidated iterator assertions. +// Invalid iterator use can trigger crashes or invalidated iterator assertions. testing::Matcher<const std::string&> InvalidIteratorMatcher() { return AnyOf(HasSubstr("invalidated iterator"), HasSubstr("Invalid iterator"), - HasSubstr("invalid iterator"), HasSubstr("use-after-free"), - HasSubstr("use-of-uninitialized-value")); + HasSubstr("invalid iterator"), + HasSubstr("CrashIfIteratorIsInvalid")); } TYPED_TEST(SooTest, IteratorInvalidAssertsEqualityOperator) {
diff --git a/absl/container/node_hash_map.h b/absl/container/node_hash_map.h index e913005..a58680a 100644 --- a/absl/container/node_hash_map.h +++ b/absl/container/node_hash_map.h
@@ -681,7 +681,8 @@ template <class Hash, bool kIsDefault> static constexpr HashSlotFn get_hash_slot_fn() { return memory_internal::IsLayoutCompatible<Key, Value>::value - ? &TypeErasedDerefAndApplyToSlotFn<Hash, Key, kIsDefault> + ? &TypeErasedDerefAndApplyToSlotFirstFn<Hash, value_type, + kIsDefault> : nullptr; } };
diff --git a/absl/crc/internal/crc_x86_arm_combined.cc b/absl/crc/internal/crc_x86_arm_combined.cc index b81490f..9e70b92 100644 --- a/absl/crc/internal/crc_x86_arm_combined.cc +++ b/absl/crc/internal/crc_x86_arm_combined.cc
@@ -746,8 +746,10 @@ CRCImpl* TryNewCRC32AcceleratedX86ARMCombined() { CpuType type = GetCpuType(); switch (type) { - case CpuType::kIntelHaswell: case CpuType::kAmdRome: + return new CRC32AcceleratedX86ARMCombinedMultipleStreams< + 3, 0, 0, CutoffStrategy::Fold3>(); + case CpuType::kIntelHaswell: case CpuType::kAmdNaples: return new CRC32AcceleratedX86ARMCombinedMultipleStreams< 3, 1, 0, CutoffStrategy::Fold3>();
diff --git a/absl/debugging/internal/stacktrace_generic-inl.inc b/absl/debugging/internal/stacktrace_generic-inl.inc index e7a11fc..c94bf3e 100644 --- a/absl/debugging/internal/stacktrace_generic-inl.inc +++ b/absl/debugging/internal/stacktrace_generic-inl.inc
@@ -71,13 +71,20 @@ size = backtrace(stack, kStackLength); skip_count++; // we want to skip the current frame as well - int result_count = size - skip_count; - if (result_count < 0) - result_count = 0; - if (result_count > max_depth) - result_count = max_depth; - for (int i = 0; i < result_count; i++) - result[i] = stack[i + skip_count]; + + int num_frames = size - skip_count; + if (num_frames < 0) num_frames = 0; + if (num_frames > max_depth) num_frames = max_depth; + + int result_count = 0; + for (int i = 0; i < num_frames; i++) { + int stack_index = i + skip_count; + // Follow x86 and stop if the return address is null (end of stack). + if (stack[stack_index] == nullptr) { + break; + } + result[result_count++] = stack[stack_index]; + } if (IS_STACK_FRAMES) { // No implementation for finding out the stack frames yet.
diff --git a/absl/log/internal/BUILD.bazel b/absl/log/internal/BUILD.bazel index ebacbc1..6995b26 100644 --- a/absl/log/internal/BUILD.bazel +++ b/absl/log/internal/BUILD.bazel
@@ -122,6 +122,11 @@ hdrs = ["flags.h"], copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + ":internal_users", + "//absl:friends", + "//absl/status:__pkg__", + ], deps = [ "//absl/flags:flag", ], @@ -467,6 +472,7 @@ visibility = [ "//absl:friends", "//absl/log:__subpackages__", + "//absl/status:__pkg__", ], deps = [ ":fnmatch",
diff --git a/absl/log/internal/log_message.cc b/absl/log/internal/log_message.cc index d253b0c..4acd503 100644 --- a/absl/log/internal/log_message.cc +++ b/absl/log/internal/log_message.cc
@@ -721,7 +721,7 @@ } // Write the string, truncating if necessary. - if (!EncodeStringTruncate(ValueTag::kString, str, &encoded_remaining_copy)) { + if (!EncodeStringTruncate(tag_value, str, &encoded_remaining_copy)) { // The length of the string itself did not fit; zero `encoded_remaining()` // so the value is not encoded at all. data_->encoded_remaining().remove_suffix(data_->encoded_remaining().size());
diff --git a/absl/status/BUILD.bazel b/absl/status/BUILD.bazel index dce316b..a8e45cb 100644 --- a/absl/status/BUILD.bazel +++ b/absl/status/BUILD.bazel
@@ -71,6 +71,7 @@ "//absl/strings:cord", "//absl/strings:str_format", "//absl/types:optional", + "//absl/types:optional_ref", "//absl/types:source_location", "//absl/types:span", ], @@ -166,6 +167,78 @@ ) cc_library( + name = "status_builder", + srcs = ["status_builder.cc"], + hdrs = ["status_builder.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":status", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:log_severity", + "//absl/strings", + "//absl/strings:cord", + "//absl/strings:internal", + "//absl/strings:stringify_stream", + "//absl/time", + "//absl/types:source_location", + ], +) + +cc_test( + name = "status_builder_test", + srcs = ["status_builder_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":status", + ":status_builder", + ":status_matchers", + ":statusor", + "//absl/base:config", + "//absl/log", + "//absl/log:flags", + "//absl/log:log_entry", + "//absl/log:log_sink", + "//absl/strings", + "//absl/types:source_location", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_library( + name = "status_macros", + hdrs = ["status_macros.h"], + deps = [ + ":status", + ":status_builder", + ":statusor", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/types:source_location", + ], +) + +cc_test( + name = "status_macros_test", + size = "small", + srcs = ["status_macros_test.cc"], + deps = [ + ":status", + ":status_builder", + ":status_macros", + ":statusor", + "//absl/base:core_headers", + "//absl/strings:string_view", + "//absl/types:source_location", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_library( name = "status_matchers", testonly = 1, srcs = [
diff --git a/absl/status/CMakeLists.txt b/absl/status/CMakeLists.txt index 8d519a5..90d4baf 100644 --- a/absl/status/CMakeLists.txt +++ b/absl/status/CMakeLists.txt
@@ -35,6 +35,7 @@ absl::core_headers absl::function_ref absl::inlined_vector + absl::internal_optional_ref absl::leak_check absl::memory absl::no_destructor @@ -111,6 +112,91 @@ absl_cc_library( NAME + status_builder + HDRS + "status_builder.h" + SRCS + "status_builder.cc" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::config + absl::cord + absl::core_headers + absl::nullability + absl::optional + absl::source_location + absl::span + absl::status + absl::strings + absl::time + PUBLIC +) + +absl_cc_test( + NAME + status_builder_test + SRCS + "status_builder_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::core_headers + absl::flags + GTest::gmock_main + absl::log + absl::log_entry + absl::log_flags + absl::log_internal_flags + absl::log_severity + absl::log_sink + absl::scoped_mock_log + absl::source_location + absl::span + absl::status + absl::statusor + absl::status_builder + absl::status_matchers + absl::strings + absl::time +) + +absl_cc_library( + NAME + status_macros + HDRS + "status_macros.h" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::core_headers + absl::source_location + absl::status + absl::status_builder + absl::statusor + PUBLIC +) + +absl_cc_test( + NAME + status_macros_test + SRCS + "status_macros_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::core_headers + absl::source_location + absl::status + absl::status_builder + absl::status_macros + absl::statusor + absl::string_view + GTest::gmock_main +) + +absl_cc_library( + NAME status_matchers HDRS "status_matchers.h"
diff --git a/absl/status/internal/status_internal.cc b/absl/status/internal/status_internal.cc index 1397de7..f58519b 100644 --- a/absl/status/internal/status_internal.cc +++ b/absl/status/internal/status_internal.cc
@@ -42,6 +42,7 @@ #include "absl/strings/str_format.h" #include "absl/strings/str_split.h" #include "absl/strings/string_view.h" +#include "absl/types/optional_ref.h" #include "absl/types/source_location.h" #include "absl/types/span.h" @@ -211,9 +212,12 @@ return true; } -StatusRep* absl_nonnull StatusRep::CloneAndUnref() const { +StatusRep* absl_nonnull StatusRep::Clone( + absl::optional_ref<absl::string_view> new_message, bool include_payloads, + bool include_source_locations) const { // Optimization: no need to create a clone if we already have a refcount of 1. - if (ref_.load(std::memory_order_acquire) == 1) { + if (ref_.load(std::memory_order_acquire) == 1 && !new_message.has_value() && + include_payloads && include_source_locations) { // All StatusRep instances are heap allocated and mutable, therefore this // const_cast will never cast away const from a stack instance. // @@ -222,12 +226,30 @@ return const_cast<StatusRep*>(this); } std::unique_ptr<status_internal::Payloads> payloads; - if (payloads_) { + if (include_payloads && payloads_) { payloads = std::make_unique<status_internal::Payloads>(*payloads_); } - auto* new_rep = new StatusRep(code_, message_, std::move(payloads)); - new_rep->source_locations_ = source_locations_; - Unref(); + auto* new_rep = + new StatusRep(code_, new_message.value_or(message_), std::move(payloads)); + if (include_source_locations) { + new_rep->source_locations_ = source_locations_; + } + return new_rep; +} + +StatusRep* absl_nonnull StatusRep::CloneAndUnref() const { + return CloneAndUnref(std::nullopt, true, true); +} + +StatusRep* absl_nonnull StatusRep::CloneAndUnref( + absl::optional_ref<absl::string_view> new_message, bool include_payloads, + bool include_source_locations) const { + StatusRep* new_rep = + Clone(new_message, /*include_payloads=*/include_payloads, + /*include_source_locations=*/include_source_locations); + if (new_rep != this) { + Unref(); + } return new_rep; }
diff --git a/absl/status/internal/status_internal.h b/absl/status/internal/status_internal.h index d5aa285..2f615e7 100644 --- a/absl/status/internal/status_internal.h +++ b/absl/status/internal/status_internal.h
@@ -29,6 +29,7 @@ #include "absl/strings/cord.h" #include "absl/strings/string_view.h" #include "absl/types/optional.h" +#include "absl/types/optional_ref.h" #include "absl/types/source_location.h" #include "absl/types/span.h" @@ -62,6 +63,7 @@ namespace status_internal { #ifndef SWIG class StatusPrivateAccessor; +class StatusPrivateAccessorForStatusBuilder; #endif // !SWIG // Container for status payloads. @@ -112,7 +114,17 @@ // Returns an equivalent heap allocated StatusRep with refcount 1. // - // `this` is not safe to be used after calling as it may have been deleted. + // If `new_message` is provided, the message will be replaced with the new + // message. + StatusRep* absl_nonnull Clone( + absl::optional_ref<absl::string_view> new_message, bool include_payloads, + bool include_source_locations) const; + + // Same as Clone(), but also removes a reference to `this`. `this` is not safe + // to be used after calling as it may have been deleted. + StatusRep* absl_nonnull CloneAndUnref( + absl::optional_ref<absl::string_view> new_message, bool include_payloads, + bool include_source_locations) const; StatusRep* absl_nonnull CloneAndUnref() const; private:
diff --git a/absl/status/status.h b/absl/status/status.h index a2cd834..88486d3 100644 --- a/absl/status/status.h +++ b/absl/status/status.h
@@ -698,6 +698,7 @@ static Status MakeNonOkStatusWithOkCode(absl::string_view message); friend class absl::status_internal::StatusPrivateAccessor; + friend class absl::status_internal::StatusPrivateAccessorForStatusBuilder; #endif // !SWIG // Creates a status in the canonical error space with the specified
diff --git a/absl/status/status_builder.cc b/absl/status/status_builder.cc new file mode 100644 index 0000000..929dead --- /dev/null +++ b/absl/status/status_builder.cc
@@ -0,0 +1,196 @@ +// Copyright 2026 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/status/status_builder.h" + +#include <cstdint> +#include <memory> +#include <ostream> +#include <string> +#include <type_traits> +#include <utility> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/macros.h" +#include "absl/status/status.h" +#include "absl/strings/cord.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" +#include "absl/types/source_location.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +void StatusBuilder::Destroy(std::unique_ptr<Rep>) { + // nothing to do. The unique_ptr will do the cleanup. +} + +// These constructors are not-inlined and defined in the .cc file to reduce +// binary size. See cl/354351433 for a quantification. +StatusBuilder::StatusBuilder() {} + +StatusBuilder::StatusBuilder(const absl::Status& original_status, + absl::SourceLocation location) + : loc_(location), rep_(InitRep(original_status)) {} + +StatusBuilder::operator absl::Status() const& { + if (rep_ == nullptr) return absl::Status(); + return CreateStatusAndConditionallyLog(loc_, std::make_unique<Rep>(*rep_)); +} + +StatusBuilder::Rep::Rep(const absl::Status& s) : status(s) {} +StatusBuilder::Rep::Rep(absl::Status&& s) : status(std::move(s)) {} +StatusBuilder::Rep::~Rep() {} + +StatusBuilder::Rep* StatusBuilder::InitRepImpl(absl::Status s) { + if (s.ok()) { + return nullptr; + } else { + return new Rep(std::move(s)); + } +} + +StatusBuilder::Rep::Rep(const Rep& r) + : status(r.status), + logging_mode(r.logging_mode), + log_severity(r.log_severity), + verbose_level(r.verbose_level), + n(r.n), + period(r.period), + stream_message(r.stream_message), + sink(r.sink), + message_join_style(r.message_join_style), + should_log_stack_trace(r.should_log_stack_trace), + also_send_to_log(r.also_send_to_log) { + if (r.stream.has_value()) { + InitStream(); + } +} + +void StatusBuilder::Rep::InitStream() { stream.emplace(stream_message); } + +bool StatusBuilder::HasPayload() const { + static constexpr absl::string_view kMessageSetUrl = + "type.googleapis.com/util.MessageSetPayload"; + return rep_ != nullptr && rep_->status.GetPayload(kMessageSetUrl).has_value(); +} + +ABSL_ATTRIBUTE_WEAK StatusBuilder& StatusBuilder::SetCode( + absl::StatusCode code) & { + if (rep_ == nullptr) { + rep_ = std::make_unique<StatusBuilder::Rep>( + absl::Status(code, absl::string_view(), absl::SourceLocation())); + } else { + absl::Status status(code, absl::string_view(), absl::SourceLocation()); + rep_->status.ForEachPayload( + [&status](absl::string_view type_url, const absl::Cord& payload) { + status.SetPayload(type_url, payload); + }); + rep_->status = std::move(status); + } + return *this; +} + +ABSL_ATTRIBUTE_WEAK void AbslInternalSetErrorCode(StatusBuilder& builder, + absl::StatusCode code) { + builder.SetCode(code); +} + +class status_internal::StatusPrivateAccessorForStatusBuilder { + public: + static absl::Status SetMessage(const absl::Status& status, + absl::string_view message) { + ABSL_ASSERT(!status.ok()); + + if (message.empty()) { + return absl::Status(status.code(), message, absl::SourceLocation()); + } + + using StatusRep = + std::remove_cv_t<std::remove_pointer_t<decltype(Status::RepToPointer( + std::declval<uintptr_t>()))>>; + StatusRep* rep; + if (Status::IsInlined(status.rep_)) { + rep = new StatusRep(Status::InlinedRepToCode(status.rep_), message, + nullptr); + } else { + rep = Status::RepToPointer(status.rep_)->Clone(message, true, true); + } + return absl::Status(Status::PointerToRep(rep)); + } + + static absl::Status JoinMessageToStatus(absl::Status s, absl::string_view msg, + MessageJoinStyle style) { + if (s.ok() || msg.empty()) return s; + const absl::string_view original_message = s.message(); + switch (style) { + case MessageJoinStyle::kAnnotate: { + std::string annotated; + if (!original_message.empty()) { + absl::StrAppend(&annotated, original_message, "; ", msg); + msg = annotated; + } + return SetMessage(s, msg); + } + case MessageJoinStyle::kPrepend: + return SetMessage(s, absl::StrCat(msg, original_message)); + case MessageJoinStyle::kAppend: + return SetMessage(s, absl::StrCat(original_message, msg)); + default: + return absl::InternalError("Unknown MessageJoinStyle"); + } + } +}; + +ABSL_ATTRIBUTE_WEAK std::string StatusBuilder::CurrentStackTrace() { + return std::string(); +} + +ABSL_ATTRIBUTE_WEAK absl::Status StatusBuilder::CreateStatusAndConditionallyLog( + absl::SourceLocation loc, std::unique_ptr<Rep> rep) { + if (rep == nullptr) return absl::OkStatus(); + absl::Status result = status_internal::StatusPrivateAccessorForStatusBuilder:: + JoinMessageToStatus(std::move(rep->status), rep->stream_message, + rep->message_join_style); + // Passing in the `loc` last to ensure the sequence of the source locations. + result.AddSourceLocation(loc); + return result; +} + +ABSL_ATTRIBUTE_WEAK std::string StatusBuilder::ToString() const { + if (rep_ == nullptr) { + return absl::OkStatus().ToString(); + } + + return status_internal::StatusPrivateAccessorForStatusBuilder:: + JoinMessageToStatus(rep_->status, rep_->stream_message, + rep_->message_join_style) + .WithSourceLocation(loc_) + .ToString(); +} + +ABSL_ATTRIBUTE_WEAK std::ostream& operator<<(std::ostream& os, + const StatusBuilder& builder) { + return os << static_cast<absl::Status>(builder); +} + +ABSL_ATTRIBUTE_WEAK std::ostream& operator<<(std::ostream& os, + StatusBuilder&& builder) { + return os << static_cast<absl::Status>(std::move(builder)); +} + +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/status/status_builder.h b/absl/status/status_builder.h new file mode 100644 index 0000000..60b7562 --- /dev/null +++ b/absl/status/status_builder.h
@@ -0,0 +1,978 @@ +// Copyright 2026 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------- +// File: status_builder.h +// ----------------------------------------------------------------------------- + +#ifndef ABSL_STATUS_STATUS_BUILDER_H_ +#define ABSL_STATUS_STATUS_BUILDER_H_ + +#include <memory> +#include <optional> +#include <ostream> +#include <string> +#include <type_traits> +#include <utility> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/log_severity.h" +#include "absl/base/optimization.h" +#include "absl/status/status.h" +#include "absl/strings/cord.h" +#include "absl/strings/internal/ostringstream.h" +#include "absl/strings/internal/stringify_stream.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" +#include "absl/types/source_location.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +class LogSink; +class StatusBuilder; + +namespace status_internal { + +// StatusBuilder's operator<< overloads use an AbslStringifyStream to allow us +// to use AbslStringify. This wraps (but does not own) an OStringStream, which +// we use for speed. We bundle them together in Stream here, partly for +// convenience in StatusBuilder's implementation, and partly to help make sure +// their lifetimes are managed correctly. +class Stream { + public: + explicit Stream(std::string& message) + : ostringstream_(&message), absl_stringify_stream_(ostringstream_) {} + + template <typename T> + friend Stream& operator<<(Stream& stream, const T& t) { + stream.absl_stringify_stream_ << t; + return stream; + } + + private: + absl::strings_internal::OStringStream ostringstream_; + absl::strings_internal::StringifyStream absl_stringify_stream_; +}; + +// StatusBuilder::With() adaptors can be classified as either "pure policy" or +// "terminal". Terminal adaptors are a mix of "side effect" or "conversion". +// We differentiate between these types by the functor's return type. +// +// This is currently for analysis only, as part of an ongoing LSC investigation. +template <typename Fn, typename Arg, typename Expected> +inline constexpr bool kResultMatches = + std::is_same_v<std::decay_t<std::invoke_result_t<Fn, Arg>>, Expected>; + +template <typename Adaptor, typename Builder> +using PurePolicy = + std::enable_if_t<kResultMatches<Adaptor, Builder, StatusBuilder>, + std::invoke_result_t<Adaptor, Builder>>; + +template <typename Adaptor, typename Builder> +using SideEffect = + std::enable_if_t<kResultMatches<Adaptor, Builder, absl::Status>, + std::invoke_result_t<Adaptor, Builder>>; + +template <typename Adaptor, typename Builder> +using Conversion = + std::enable_if_t<!kResultMatches<Adaptor, Builder, StatusBuilder> && + !kResultMatches<Adaptor, Builder, absl::Status>, + std::invoke_result_t<Adaptor, Builder>>; + +class StatusBuilderPrivateAccessor; + +} // namespace status_internal + +// Internal argument-dependent lookup extension point for StatusBuilder. +// Necessary for allowing StatusBuilder to be open-sourced into Abseil without +// introducing hard dependencies on non-canonical error spaces or protobuf. +// +// What we would have preferred here is to just leave +// StatusBuilder::SetErrorCode() undeclared for OSS users, but defined for +// internal users. However, C++ doesn't provide a great way to do that directly, +// since merely omitting a definition still leaves it declared and thus +// accessible. +// +// We therefore use this ADL extension point as the closest moral equivalent: +// this allows use to define a simple shell for the method in OSS, but to +// prevent its actual usage (via SFINAE) unless this extension point is also +// defined, which is only the case in our internal libraries. +void AbslInternalSetErrorCode(StatusBuilder&, absl::StatusCode); + +// Specifies how to join the error message in the original status and any +// additional message that has been streamed into the builder. +enum class MessageJoinStyle { + kAnnotate, + kAppend, + kPrepend, +}; + +// Creates a status based on an original_status, but enriched with additional +// information. The builder implicitly converts to Status and StatusOr<T> +// allowing for it to be returned directly. +// +// StatusBuilder builder(original); +// builder.SetPayload(proto); +// builder << "info about error"; +// return builder; +// +// It provides method chaining to simplify typical usage: +// +// return StatusBuilder(original) +// .Log(absl::LogSeverity::kWarning) << "oh no!"; +// +// In more detail: +// - When the original status is OK, all methods become no-ops and nothing will +// be logged. +// - Messages streamed into the status builder are collected into a single +// additional message string. +// - The original Status's message and the additional message are joined +// together when the result status is built. +// - By default, the messages will be joined as if by `util::Annotate`, which +// includes a convenience separator between the original message and the +// additional one. This behavior can be changed with the `SetAppend()` and +// `SetPrepend()` methods of the builder. +// - By default, the result status is not logged. The `Log` and +// `EmitStackTrace` methods will cause the builder to log the result status +// when it is built. +// - All side effects (like logging or constructing a stack trace) happen when +// the builder is converted to a status. +class ABSL_MUST_USE_RESULT StatusBuilder { + public: + explicit StatusBuilder(); + ~StatusBuilder(); + + // Creates a `StatusBuilder` based on an original status. If logging is + // enabled, it will use `location` as the location from which the log message + // occurs. A typical user will not specify `location`, allowing it to default + // to the current location. + explicit StatusBuilder( + const absl::Status& original_status, + absl::SourceLocation location = absl::SourceLocation::current()); + explicit StatusBuilder( + absl::Status&& original_status, + absl::SourceLocation location = absl::SourceLocation::current()); + + // Creates a `StatusBuilder` from a status code. If logging is enabled, it + // will use `location` as the location from which the log message occurs. A + // typical user will not specify `location`, allowing it to default to the + // current location. + explicit StatusBuilder( + absl::StatusCode code, + absl::SourceLocation location = absl::SourceLocation::current()); + + StatusBuilder(const StatusBuilder& sb); + StatusBuilder& operator=(const StatusBuilder& sb); + StatusBuilder(StatusBuilder&&) = default; + StatusBuilder& operator=(StatusBuilder&&) = default; + + // Mutates the builder so that the final additional message is prepended to + // the original error message in the status. A convenience separator is not + // placed between the messages. + // + // NOTE: Multiple calls to `SetPrepend` and `SetAppend` just adjust the + // behavior of the final join of the original status with the extra message. + // + // Returns `*this` to allow method chaining. + StatusBuilder& SetPrepend() &; + ABSL_MUST_USE_RESULT StatusBuilder&& SetPrepend() &&; + + // Mutates the builder so that the final additional message is appended to the + // original error message in the status. A convenience separator is not + // placed between the messages. + // + // NOTE: Multiple calls to `SetPrepend` and `SetAppend` just adjust the + // behavior of the final join of the original status with the extra message. + // + // Returns `*this` to allow method chaining. + StatusBuilder& SetAppend() &; + ABSL_MUST_USE_RESULT StatusBuilder&& SetAppend() &&; + + // Mutates the builder to disable any logging that was set using any of the + // logging functions below. Returns `*this` to allow method chaining. + StatusBuilder& SetNoLogging() &; + ABSL_MUST_USE_RESULT StatusBuilder&& SetNoLogging() &&; + + // Mutates the builder so that the result status will be logged (without a + // stack trace) when this builder is converted to a Status. This overrides + // the logging settings from earlier calls to any of the logging mutator + // functions. Returns `*this` to allow method chaining. + StatusBuilder& Log(absl::LogSeverity level) &; + ABSL_MUST_USE_RESULT StatusBuilder&& Log(absl::LogSeverity level) &&; + + StatusBuilder& LogError() & { return Log(absl::LogSeverity::kError); } + ABSL_MUST_USE_RESULT StatusBuilder&& LogError() && { + return std::move(LogError()); + } + StatusBuilder& LogWarning() & { return Log(absl::LogSeverity::kWarning); } + ABSL_MUST_USE_RESULT StatusBuilder&& LogWarning() && { + return std::move(LogWarning()); + } + StatusBuilder& LogInfo() & { return Log(absl::LogSeverity::kInfo); } + ABSL_MUST_USE_RESULT StatusBuilder&& LogInfo() && { + return std::move(LogInfo()); + } + + // Mutates the builder so that the result status will be logged every N + // invocations (without a stack trace) when this builder is converted to a + // Status. This overrides the logging settings from earlier calls to any of + // the logging mutator functions. Returns `*this` to allow method chaining. + StatusBuilder& LogEveryN(absl::LogSeverity level, int n) &; + ABSL_MUST_USE_RESULT StatusBuilder&& LogEveryN(absl::LogSeverity level, + int n) &&; + + // Mutates the builder so that the result status will be logged once per + // period (without a stack trace) when this builder is converted to a Status. + // This overrides the logging settings from earlier calls to any of the + // logging mutator functions. Returns `*this` to allow method chaining. + // If period is absl::ZeroDuration() or less, then this is equivalent to + // calling the Log() method. + StatusBuilder& LogEvery(absl::LogSeverity level, absl::Duration period) &; + ABSL_MUST_USE_RESULT StatusBuilder&& LogEvery(absl::LogSeverity level, + absl::Duration period) &&; + + // Mutates the builder so that the result status will be VLOGged (without a + // stack trace) when this builder is converted to a Status. `verbose_level` + // indicates the verbosity level that would be passed to VLOG(). This + // overrides the logging settings from earlier calls to any of the logging + // mutator functions. Returns `*this` to allow method chaining. + StatusBuilder& VLog(int verbose_level) &; + ABSL_MUST_USE_RESULT StatusBuilder&& VLog(int verbose_level) &&; + + // Mutates the builder so that a stack trace will be logged if the status is + // logged. One of the logging setters above should be called as well. If + // logging is not yet enabled this behaves as if LogInfo().EmitStackTrace() + // was called. Returns `*this` to allow method chaining. + StatusBuilder& EmitStackTrace() &; + ABSL_MUST_USE_RESULT StatusBuilder&& EmitStackTrace() &&; + + // Mutates the builder so that the result status will also be logged to the + // provided `sink` when this builder is converted to a status. Overwrites any + // sink set prior. The provided `sink` must point to a valid object by the + // time this builder is converted to a status. Has no effect if this builder + // is not configured to log by calling any of the LogXXX methods. Returns + // `*this` to allow method chaining. + StatusBuilder& AlsoOutputToSink(absl::LogSink* sink) &; + ABSL_MUST_USE_RESULT StatusBuilder&& AlsoOutputToSink(absl::LogSink* sink) &&; + + // Mutates the builder so that the result status will only be logged to the + // provided `sink` when this builder is converted to a status. Overwrites any + // sink set prior. The provided `sink` must point to a valid object by the + // time this builder is converted to a status. Has no effect if this builder + // is not configured to log by calling any of the LogXXX methods. Returns + // `*this` to allow method chaining. + StatusBuilder& OnlyOutputToSink(absl::LogSink* sink) &; + ABSL_MUST_USE_RESULT StatusBuilder&& OnlyOutputToSink(absl::LogSink* sink) &&; + + // Appends to the extra message that will be added to the original status. By + // default, the extra message is added to the original message as if by + // `util::Annotate`, which includes a convenience separator between the + // original message and the enriched one. + template <typename T> + StatusBuilder& operator<<(const T& value) &; + + template <typename T> + ABSL_MUST_USE_RESULT StatusBuilder&& operator<<(const T& value) &&; + + // Adds a payload for the status that will be returned by this StatusBuilder. + // Note that this is equivalent to `Status::SetPayload`, to attach protos to + // the MessageSet payload, use `util::AttachPayload`. Returns '*this' to allow + // method chaining. + StatusBuilder& SetPayload(absl::string_view type_url, absl::Cord payload) &; + ABSL_MUST_USE_RESULT StatusBuilder&& SetPayload(absl::string_view type_url, + absl::Cord payload) && { + return std::move(SetPayload(type_url, std::move(payload))); + } + + std::optional<absl::Cord> GetPayload(absl::string_view type_url) const; + + // INTERNAL API. NOT FOR PUBLIC USE. + template <typename MessageSetExtension, typename ExtensionIdentifier> + auto AttachPayload(const MessageSetExtension& obj, + const ExtensionIdentifier& id) & // + -> std::enable_if_t< + std::is_void_v<decltype(AbslInternalAttachPayload(*this, obj, id))>, + StatusBuilder&> { + AbslInternalAttachPayload(*this, obj, id); + return *this; + } + + // As above, but &&-qualified. + // + // Uses decltype() to propagate template constraints (SFINAE). + template <typename MessageSetExtension, typename ExtensionIdentifier> + ABSL_MUST_USE_RESULT auto AttachPayload(const MessageSetExtension& obj, + const ExtensionIdentifier& id) && // + -> decltype(std::move(AttachPayload(obj, id))) { + return std::move(AttachPayload(obj, id)); + } + + // Like `AttachPayload() &`, but with the default extension ID. + // + // INTERNAL API. NOT FOR PUBLIC USE. + template <typename MessageSetExtension> + auto AttachPayload(const MessageSetExtension& obj) & // + -> std::enable_if_t< + std::is_void_v<decltype(AbslInternalAttachPayload(*this, obj))>, + StatusBuilder&> { + AbslInternalAttachPayload(*this, obj); + return *this; + } + + // As above, but &&-qualified. + // + // Uses decltype() to propagate template constraints (SFINAE). + template <typename MessageSetExtension> + ABSL_MUST_USE_RESULT auto AttachPayload(const MessageSetExtension& obj) && // + -> decltype(std::move(AttachPayload(obj))) { + return std::move(AttachPayload(obj)); + } + + // HasPayload() + // + // Indicates whether the Status object that will be returned by the + // StatusBuilder contains any payloads with a type extending proto2's + // `MessageSet`, returning `true` if so. Having a payload does not guarantee + // the presence of a payload with a specific type. Note that returning `false` + // does not necessarily indicate the absence of a payload, but only the + // absence on one which extends `MessageSet`. + bool HasPayload() const; + + // INTERNAL API. NOT FOR PUBLIC USE. + template <typename Enum> + auto SetErrorCode(Enum code) & // + -> std::enable_if_t< + std::is_void_v<decltype(AbslInternalSetErrorCode(*this, code))>, + StatusBuilder&> { + AbslInternalSetErrorCode(*this, code); + return *this; + } + + // As above, but &&-qualified. + // + // Uses decltype() to propagate template constraints (SFINAE). + template <typename Enum> + ABSL_MUST_USE_RESULT auto SetErrorCode(Enum code) && // + -> decltype(std::move(SetErrorCode(code))) { + return std::move(SetErrorCode(code)); + } + + // Sets the status code for the status that will be returned by this + // StatusBuilder. Returns `*this` to allow method chaining. + StatusBuilder& SetCode(absl::StatusCode code) &; + ABSL_MUST_USE_RESULT StatusBuilder&& SetCode(absl::StatusCode code) && { + return std::move(SetCode(code)); + } + + ///////////////////////////////// Adaptors ///////////////////////////////// + // + // A StatusBuilder `adaptor` is a functor which can be included in a builder + // method chain. There are two common variants: + // + // 1. `Pure policy` adaptors modify the StatusBuilder and return the modified + // object, which can then be chained with further adaptors or mutations. + // + // 2. `Terminal` adaptors consume the builder's Status and return some + // other type of object. Alternatively, the consumed Status may be used + // for side effects, e.g. by passing it to a side channel. A terminal + // adaptor cannot be chained. + // + // Careful: The conversion of StatusBuilder to Status has side effects! + // Adaptors must ensure that this conversion happens at most once in the + // builder chain. The easiest way to do this is to determine the adaptor type + // and follow the corresponding guidelines: + // + // Pure policy adaptors should: + // (a) Take a StatusBuilder as input parameter. + // (b) NEVER convert the StatusBuilder to Status: + // - Never assign the builder to a Status variable. + // - Never pass the builder to a function whose parameter type is Status, + // including by reference (e.g. const Status&). + // - Never pass the builder to any function which might convert the + // builder to Status (i.e. this restriction is viral). + // (c) Return a StatusBuilder (usually the input parameter). + // + // Terminal adaptors should: + // (a) Take a Status as input parameter (not a StatusBuilder!). + // (b) Return a type matching the enclosing function. (This can be `void`.) + // + // Adaptors do not necessarily fit into one of these categories. However, any + // which satisfies the conversion rule can always be decomposed into a pure + // adaptor chained into a terminal adaptor. (This is recommended.) + // + // Examples + // + // Pure adaptors allow teams to configure team-specific error handling + // policies. For example: + // + // StatusBuilder TeamPolicy(StatusBuilder builder) { + // builder.SetPayload(...); + // return std::move(builder).Log(absl::LogSeverity::kWarning); + // } + // + // ABSL_RETURN_IF_ERROR(foo()).With(TeamPolicy); + // + // Because pure policy adaptors return the modified StatusBuilder, they + // can be chained with further adaptors, e.g.: + // + // ABSL_RETURN_IF_ERROR(foo()).With(TeamPolicy).With(OtherTeamPolicy); + // + // Terminal adaptors are often used for type conversion. This allows + // ABSL_RETURN_IF_ERROR to be used in functions which do not return Status. + // For example, a function might want to return some default value on error: + // + // int GetSysCounter() { + // int value; + // ABSL_RETURN_IF_ERROR(ReadCounterFile(filename, &value)) + // .LogInfo() + // .With([](const absl::Status& unused) { return 0; }); + // return value; + // } + // + // For the simple case of returning a constant (e.g. zero, false, nullptr) on + // error, consider `status_macros::Return` or `status_macros::ReturnVoid`: + // + // #include "util/task/contrib/status_macros/return.h" + // + // bool DoMyThing() { + // ABSL_RETURN_IF_ERROR(foo()).LogWarning().With(status_macros::Return(false)); + // ... + // } + // + // A terminal adaptor may instead (or additionally) be used to create side + // effects that are not supported natively by `StatusBuilder`, such as + // returning the Status through a side channel. For example, + // `util::TaskReturn` returns the Status through the `util::Task` that it was + // initialized with. This adaptor then returns `void`, to match the typical + // return type of functions that maintain state through `util::Task`: + // + // class TaskReturn { + // public: + // explicit TaskReturn(Task* t) : task_(t) {} + // void operator()(const Status& status) const { task_->Return(status); } + // // ... + // }; + // + // void Read(absl::string_view name, util::Task* task) { + // int64 id; + // ABSL_RETURN_IF_ERROR(GetIdForName(name, &id)).With(TaskReturn(task)); + // ABSL_RETURN_IF_ERROR(ReadForId(id)).With(TaskReturn(task)); + // task->Return(); + // } + + // Calls `adaptor` on this status builder to apply policies, type conversions, + // and/or side effects on the StatusBuilder. Returns the value returned by + // `adaptor`, which may be any type including `void`. See comments above. + // + // Style guide exception for Ref qualified methods and rvalue refs + // (cl/128258530). This allows us to avoid a copy in the common case. + // + // Note: All With() overrides are equivalent, and return Adaptor(this). They + // are part of an ongoing LSC investigation. + template <typename Adaptor> + auto With(Adaptor&& adaptor) & -> status_internal::PurePolicy< + Adaptor, StatusBuilder&> { + return std::forward<Adaptor>(adaptor)(*this); + } + template <typename Adaptor> + ABSL_MUST_USE_RESULT auto With( + Adaptor&& + adaptor) && -> status_internal::PurePolicy<Adaptor, StatusBuilder&&> { + return std::forward<Adaptor>(adaptor)(std::move(*this)); + } + + template <typename Adaptor> + auto With(Adaptor&& adaptor) & -> status_internal::SideEffect< + Adaptor, StatusBuilder&> { + return std::forward<Adaptor>(adaptor)(*this); + } + template <typename Adaptor> + ABSL_MUST_USE_RESULT auto With( + Adaptor&& + adaptor) && -> status_internal::SideEffect<Adaptor, StatusBuilder&&> { + return std::forward<Adaptor>(adaptor)(std::move(*this)); + } + + template <typename Adaptor> + auto With(Adaptor&& adaptor) & -> status_internal::Conversion< + Adaptor, StatusBuilder&> { + return std::forward<Adaptor>(adaptor)(*this); + } + template <typename Adaptor> + ABSL_MUST_USE_RESULT auto With( + Adaptor&& + adaptor) && -> status_internal::Conversion<Adaptor, StatusBuilder&&> { + return std::forward<Adaptor>(adaptor)(std::move(*this)); + } + + // Returns true if the Status created by this builder will be ok(). + ABSL_MUST_USE_RESULT bool ok() const; + + // Returns the (canonical) error code for the Status created by this builder. + absl::StatusCode code() const; + + // Implicit conversion to Status. + // + // Careful: this operator has side effects, so it should be called at + // most once. In particular, do NOT use this conversion operator to inspect + // the status from an adapter object passed into With(). + // + // Style guide exception for using Ref qualified methods and for implicit + // conversions (cl/124566728). This override allows us to implement + // ABSL_RETURN_IF_ERROR with 2 move operations in the common case. + operator absl::Status() const&; // NOLINT: Builder converts implicitly. + operator absl::Status() &&; // NOLINT: Builder converts implicitly. + + // Returns the source location used to create this builder. This differs from + // `GetPreviousSourceLocations()` as this location is the single location for + // the creation of this builder. + ABSL_MUST_USE_RESULT absl::SourceLocation source_location() const; + + // Returns the source locations previously recorded in the status. This will + // not include the source location of the `StatusBuilder` itself (which is + // available via `source_location()`). When the builder is converted to a + // Status, the builder's location will be appended to this list of previous + // locations. For `StatusBuilder`s created without an original status (e.g., + // from a status code), this will be empty. + decltype(auto) GetPreviousSourceLocations() const { + if (rep_ == nullptr) { + return absl::OkStatus().GetSourceLocations(); + } + return rep_->status.GetSourceLocations(); + } + + // Returns a string based on the `mode`. This produces the same string as + // would converting to a Status and calling operator<<, but it does so without + // side effects (e.g., logging). Therefore, it is safe to use from within an + // adapter object passed into With(). + std::string ToString() const; + + private: + friend class status_internal::StatusBuilderPrivateAccessor; + + // Returns true if the compiler can determine that the instance is empty. That + // is, `rep_ == nullptr`. + // A `false` return does not necessarily indicate that it has a rep. It just + // can't prove it doesn't. + ABSL_ATTRIBUTE_ALWAYS_INLINE bool IsKnownToBeEmpty() const { +#if ABSL_HAVE_BUILTIN(__builtin_constant_p) + // __builtin_constant_p does not like it when it has non trivial expressions + // in it, and `rep_==nullptr` is a user-defined operator. + // Do it out of the builtin and pass the bool instead. + bool is_empty = rep_ == nullptr; + return __builtin_constant_p(is_empty) && is_empty; +#else + return false; +#endif + } + + // Tells the compiler that it can assume that `rep_` is null. + // This is verified in non-opt mode. + void AssumeEmpty() const { + if (rep_ != nullptr) ABSL_UNREACHABLE(); + } + + static std::string CurrentStackTrace(); + + struct Rep; + // Destroy the `Rep` object. It is just calling the destructor of the + // `unique_ptr`, but out of line. + static void Destroy(std::unique_ptr<Rep>); + + // Creates a Status from this builder and logs it if the builder has been + // configured to log itself. + // NOTE: This function is `static` to prevent escaping the `this` pointer. We + // transfer the `Rep` to delegate the destruction. + static absl::Status CreateStatusAndConditionallyLog(absl::SourceLocation loc, + std::unique_ptr<Rep> rep); + + // Infrequently set builder options, instantiated lazily. This reduces + // average construction/destruction time (e.g. the `stream` is fairly + // expensive). Stacks can also be blown if StatusBuilder grows too large. + // This is primarily an issue for debug builds, which do not necessarily + // re-use stack space within a function across the sub-scopes used by + // status macros. + struct Rep { + explicit Rep(const absl::Status& s); + explicit Rep(absl::Status&& s); + Rep(const Rep& r); + ~Rep(); + void InitStream(); + + // The status that the result will be based on. Can be modified by + // SetPayload(). + absl::Status status; + + enum class LoggingMode { + kDisabled, + kLog, + kVLog, + kLogEveryN, + kLogEveryPeriod + }; + LoggingMode logging_mode = LoggingMode::kDisabled; + + // The severity level at which the Status should be logged. Note that + // `logging_mode == LoggingMode::kVLog` always logs at severity INFO. + absl::LogSeverity log_severity; + + // The level at which the Status should be VLOGged. + // Only used when `logging_mode == LoggingMode::kVLog`. + int verbose_level; + + // Only log every N invocations. + // Only used when `logging_mode == LoggingMode::kLogEveryN`. + int n; + + // Only log once per period. + // Only used when `logging_mode == LoggingMode::kLogEveryPeriod`. + absl::Duration period; + + // Gathers additional messages added with `<<` for use in the final status. + std::string stream_message; + std::optional<status_internal::Stream> stream; + + // If not nullptr, specifies the log sink where log output should be + // sent to. Only used when `logging_mode != LoggingMode::kDisabled`. + absl::LogSink* sink = nullptr; + + // Specifies how to join the message in `status` and `stream`. + MessageJoinStyle message_join_style = MessageJoinStyle::kAnnotate; + + // Whether to log stack trace. Only used when `logging_mode != + // LoggingMode::kDisabled`. + bool should_log_stack_trace = false; + + // When this is true, will send both to `sink` and will log. When this is + // false, will only send to `sink`. + bool also_send_to_log = true; + }; + + static Rep* InitRep(const absl::Status& s) { + if (s.ok()) { + return nullptr; + } else { + return new Rep(s); + } + } + + static Rep* InitRep(absl::Status&& s) { + if (s.ok()) { + return nullptr; + } else { + Rep* rep = InitRepImpl(std::move(s)); + ABSL_ASSUME(rep != nullptr); + return rep; + } + } + + // Out of line function that constructs the Rep. + // Note that `s` is by value because of the TRIVIAL_ABI. + static Rep* InitRepImpl(absl::Status s); + + // The location to record if this status is logged. + absl::SourceLocation loc_; + + // nullptr if the result status will be OK. Extra fields moved to the heap to + // minimize stack space. + std::unique_ptr<Rep> rep_; + + // For testing Rep's implememntation. + friend class StatusBuilderTest; +}; + +// Implicitly converts `builder` to `Status` and write it to `os`. +std::ostream& operator<<(std::ostream& os, const StatusBuilder& builder); +std::ostream& operator<<(std::ostream& os, StatusBuilder&& builder); + +// StatusBuilder policy to append an extra message to the original status. +// +// This is most useful with adaptors such as util::TaskReturn that otherwise +// would prevent use of operator<<. For example: +// +// ABSL_RETURN_IF_ERROR(foo(val)) +// .With(util::ExtraMessage("when calling foo()")) +// .With(util::TaskReturn(task)); +// +// or +// +// ABSL_RETURN_IF_ERROR(foo(val)) +// .With(util::ExtraMessage() << "val: " << val) +// .With(util::TaskReturn(task)); +// +// Note in the above example, the ABSL_RETURN_IF_ERROR macro ensures the +// ExtraMessage expression is evaluated only in the error case, so efficiency of +// constructing the message is not a concern in the success case. +class ExtraMessage { + public: + ExtraMessage() : ExtraMessage(std::string()) {} + explicit ExtraMessage(std::string msg) + : msg_(std::move(msg)), stream_(msg_) {} + + ExtraMessage( + ExtraMessage&& other) noexcept // strings::OStringStream is stateless + // so we can simply move over the message. + : ExtraMessage(std::move(other.msg_)) {} + + // Appends to the extra message that will be added to the original status. By + // default, the extra message is added to the original message as if by + // `util::Annotate`, which includes a convenience separator between the + // original message and the enriched one. + template <typename T> + ExtraMessage& operator<<(const T& value) & { + stream_ << value; + return *this; + } + + // As above, preserving the rvalue-ness of the ExtraMessage object. + template <typename T> + ExtraMessage&& operator<<(const T& value) && { + *this << value; + return std::move(*this); + } + + // Appends to the extra message that will be added to the original status. By + // default, the extra message is added to the original message as if by + // `util::Annotate`, which includes a convenience separator between the + // original message and the enriched one. + StatusBuilder operator()(StatusBuilder builder) const { + builder << msg_; + return builder; + } + + private: + std::string msg_; + status_internal::Stream stream_; +}; + +// Implementation details follow; clients should ignore. + +inline StatusBuilder::StatusBuilder(absl::StatusCode code, + absl::SourceLocation location) + : loc_(location), rep_(InitRep(absl::Status(code, ""))) {} + +inline StatusBuilder::StatusBuilder(const StatusBuilder& sb) : loc_(sb.loc_) { + if (sb.rep_ != nullptr) { + rep_ = std::make_unique<Rep>(*sb.rep_); + } +} + +inline StatusBuilder::StatusBuilder(absl::Status&& original_status, + absl::SourceLocation location) + : loc_(location), rep_(InitRep(std::move(original_status))) {} + +inline StatusBuilder::~StatusBuilder() { + if (IsKnownToBeEmpty()) { + // Nothing to do. + return; + } + // We will run the destructor logic, so move it out of line. + // The destructor of the unique_ptr runs ~Rep() and then ::operator delete. + // We don't want that bloat on the caller. + Destroy(std::move(rep_)); + // Tell the compiler that `rep_` was not filled again even if `this` escaped. + AssumeEmpty(); +} + +inline StatusBuilder& StatusBuilder::operator=(const StatusBuilder& sb) { + loc_ = sb.loc_; + if (sb.rep_ != nullptr) { + rep_ = std::make_unique<Rep>(*sb.rep_); + } else { + rep_ = nullptr; + } + return *this; +} + +inline StatusBuilder& StatusBuilder::SetPrepend() & { + if (rep_ == nullptr) return *this; + rep_->message_join_style = MessageJoinStyle::kPrepend; + return *this; +} +inline StatusBuilder&& StatusBuilder::SetPrepend() && { + return std::move(SetPrepend()); +} + +inline StatusBuilder& StatusBuilder::SetAppend() & { + if (rep_ == nullptr) return *this; + rep_->message_join_style = MessageJoinStyle::kAppend; + return *this; +} +inline StatusBuilder&& StatusBuilder::SetAppend() && { + return std::move(SetAppend()); +} + +inline StatusBuilder& StatusBuilder::SetNoLogging() & { + if (rep_ != nullptr) { + rep_->logging_mode = Rep::LoggingMode::kDisabled; + rep_->should_log_stack_trace = false; + } + return *this; +} +inline StatusBuilder&& StatusBuilder::SetNoLogging() && { + return std::move(SetNoLogging()); +} + +inline StatusBuilder& StatusBuilder::Log(absl::LogSeverity level) & { + if (rep_ == nullptr) return *this; + rep_->logging_mode = Rep::LoggingMode::kLog; + rep_->log_severity = level; + return *this; +} +inline StatusBuilder&& StatusBuilder::Log(absl::LogSeverity level) && { + return std::move(Log(level)); +} + +inline StatusBuilder& StatusBuilder::LogEveryN(absl::LogSeverity level, + int n) & { + if (rep_ == nullptr) return *this; + if (n < 1) return Log(level); + rep_->logging_mode = Rep::LoggingMode::kLogEveryN; + rep_->log_severity = level; + rep_->n = n; + return *this; +} +inline StatusBuilder&& StatusBuilder::LogEveryN(absl::LogSeverity level, + int n) && { + return std::move(LogEveryN(level, n)); +} + +inline StatusBuilder& StatusBuilder::LogEvery(absl::LogSeverity level, + absl::Duration period) & { + if (rep_ == nullptr) return *this; + if (period <= absl::ZeroDuration()) return Log(level); + rep_->logging_mode = Rep::LoggingMode::kLogEveryPeriod; + rep_->log_severity = level; + rep_->period = period; + return *this; +} +inline StatusBuilder&& StatusBuilder::LogEvery(absl::LogSeverity level, + absl::Duration period) && { + return std::move(LogEvery(level, period)); +} + +inline StatusBuilder& StatusBuilder::VLog(int verbose_level) & { + if (rep_ == nullptr) return *this; + rep_->logging_mode = Rep::LoggingMode::kVLog; + rep_->verbose_level = verbose_level; + return *this; +} +inline StatusBuilder&& StatusBuilder::VLog(int verbose_level) && { + return std::move(VLog(verbose_level)); +} + +inline StatusBuilder& StatusBuilder::EmitStackTrace() & { + if (rep_ == nullptr) return *this; + if (rep_->logging_mode == Rep::LoggingMode::kDisabled) { + // Default to INFO logging, otherwise nothing would be emitted. + rep_->logging_mode = Rep::LoggingMode::kLog; + rep_->log_severity = absl::LogSeverity::kInfo; + } + rep_->should_log_stack_trace = true; + return *this; +} +inline StatusBuilder&& StatusBuilder::EmitStackTrace() && { + return std::move(EmitStackTrace()); +} + +inline StatusBuilder& StatusBuilder::AlsoOutputToSink(absl::LogSink* sink) & { + if (rep_ == nullptr) return *this; + rep_->sink = sink; + rep_->also_send_to_log = true; + return *this; +} +inline StatusBuilder&& StatusBuilder::AlsoOutputToSink(absl::LogSink* sink) && { + return std::move(AlsoOutputToSink(sink)); +} +inline StatusBuilder& StatusBuilder::OnlyOutputToSink(absl::LogSink* sink) & { + if (rep_ == nullptr) return *this; + rep_->sink = sink; + rep_->also_send_to_log = false; + return *this; +} +inline StatusBuilder&& StatusBuilder::OnlyOutputToSink(absl::LogSink* sink) && { + return std::move(OnlyOutputToSink(sink)); +} + +template <typename T> +StatusBuilder& StatusBuilder::operator<<(const T& value) & { + if (rep_ == nullptr) return *this; + if (!rep_->stream.has_value()) { + rep_->InitStream(); + } + *rep_->stream << value; + return *this; +} + +template <typename T> +StatusBuilder&& StatusBuilder::operator<<(const T& value) && { + return std::move(operator<<(value)); +} + +inline StatusBuilder& StatusBuilder::SetPayload(absl::string_view type_url, + absl::Cord payload) & { + if (rep_ != nullptr) { + rep_->status.SetPayload(type_url, std::move(payload)); + } + return *this; +} + +inline std::optional<absl::Cord> StatusBuilder::GetPayload( + absl::string_view type_url) const { + return rep_ == nullptr ? std::nullopt : rep_->status.GetPayload(type_url); +} + +inline bool StatusBuilder::ok() const { + return rep_ == nullptr ? true : rep_->status.ok(); +} + +inline absl::StatusCode StatusBuilder::code() const { + return rep_ == nullptr ? absl::StatusCode::kOk : rep_->status.code(); +} + +inline StatusBuilder::operator absl::Status() && { + // Tell the compiler that the `ok()` path will return an `Ok` status, but do + // it only if the compiler can determine it at compile time. + // When it can't, we delegate this check into the out of line function. + if (IsKnownToBeEmpty()) { + return absl::OkStatus(); + } + absl::Status result = CreateStatusAndConditionallyLog(loc_, std::move(rep_)); + // Tell the compiler that `rep_` was not filled again even if `this` escaped. + AssumeEmpty(); + return result; +} + +inline absl::SourceLocation StatusBuilder::source_location() const { + return loc_; +} + +// HasPayload() +// +// Indicates whether the Status object that will be returned by the +// StatusBuilder contains any payloads with a type extending proto2's +// `MessageSet`, returning `true` if so. Having a payload does not guarantee the +// presence of a payload with a specific type. Note that returning `false` does +// not necessarily indicate the absence of a payload, but only the absence on +// one which extends `MessageSet`. +inline bool HasPayload(const StatusBuilder& builder) { + return builder.HasPayload(); +} + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_STATUS_STATUS_BUILDER_H_
diff --git a/absl/status/status_builder_test.cc b/absl/status/status_builder_test.cc new file mode 100644 index 0000000..28fd6b9 --- /dev/null +++ b/absl/status/status_builder_test.cc
@@ -0,0 +1,380 @@ +// Copyright 2026 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/status/status_builder.h" + +#include <stddef.h> + +#include <memory> +#include <string> +#include <type_traits> +#include <utility> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/config.h" +#include "absl/log/log.h" +#include "absl/log/log_entry.h" +#include "absl/log/log_sink.h" +#include "absl/status/status.h" +#include "absl/status/status_matchers.h" +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "absl/types/source_location.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace { + +using ::absl_testing::StatusIs; +using ::testing::AllOf; +using ::testing::AnyOf; +using ::testing::ElementsAre; +using ::testing::Eq; +using ::testing::IsEmpty; +using ::testing::Pointee; +using ::testing::Property; + +// Converts a StatusBuilder to a Status. +absl::Status ToStatus(const StatusBuilder& s) { return s; } + +// Converts a StatusBuilder to a StatusOr<T>. +template <typename T> +absl::StatusOr<T> ToStatusOr(const StatusBuilder& s) { + return s; +} + +void CheckSourceLocation( + const absl::Status& status, std::vector<int> lines = {}, + absl::SourceLocation loc = absl::SourceLocation::current()) { + ASSERT_EQ(status.GetSourceLocations().size(), lines.size()) + << "Size check failed at " << loc.line(); + for (size_t i = 0; i < lines.size(); ++i) { + EXPECT_EQ(absl::string_view(status.GetSourceLocations()[i].file_name()), + absl::string_view(loc.file_name())) + << "File name check failed at " << loc.line(); + EXPECT_EQ(status.GetSourceLocations()[i].line(), lines[i]) + << "Line check failed at " << loc.line(); + } +} + +class StatusBuilderTest : public ::testing::Test {}; + +TEST_F(StatusBuilderTest, Size) { + EXPECT_LE(sizeof(StatusBuilder), 40) + << "Relax this test with caution and thorough testing. If StatusBuilder " + "is too large it can potentially blow stacks, especially in debug " + "builds. See the comments for StatusBuilder::Rep."; +} + +TEST_F(StatusBuilderTest, ExplicitSourceLocation) { + const absl::SourceLocation kLocation = absl::SourceLocation::current(); + + { + const StatusBuilder builder(absl::OkStatus(), kLocation); + EXPECT_THAT(builder.source_location().file_name(), + Eq(kLocation.file_name())); + EXPECT_THAT(builder.source_location().line(), Eq(kLocation.line())); + } +} + +TEST_F(StatusBuilderTest, ImplicitSourceLocation) { + const StatusBuilder builder(absl::OkStatus()); + auto loc = absl::SourceLocation::current(); + EXPECT_THAT(builder.source_location().file_name(), + AnyOf(Eq(absl::string_view(loc.file_name())), + Eq(absl::string_view("<source_location>")))); + EXPECT_THAT(builder.source_location().line(), + AnyOf(Eq(1), Eq(loc.line() - 1))); +} + +testing::Matcher<absl::SourceLocation> SourceLocationIs( + absl::SourceLocation loc) { + return AnyOf( + AllOf(Property(&absl::SourceLocation::file_name, Eq(loc.file_name())), + Property(&absl::SourceLocation::line, Eq(loc.line()))), + // Fallback for platforms that don't support source locations. + AllOf(Property(&absl::SourceLocation::file_name, Eq("<source_location>")), + Property(&absl::SourceLocation::line, Eq(1)))); +} + +TEST_F(StatusBuilderTest, GetPreviousSourceLocations) { + const absl::SourceLocation loc0 = absl::SourceLocation::current(); + absl::Status status = absl::InvalidArgumentError("hi", loc0); + const absl::SourceLocation loc1 = absl::SourceLocation::current(); + status.AddSourceLocation(loc1); + const absl::SourceLocation loc2 = absl::SourceLocation::current(); + status.AddSourceLocation(loc2); + + // The builder's location is not included. + const StatusBuilder builder(status); + EXPECT_THAT(builder.GetPreviousSourceLocations(), + ElementsAre(SourceLocationIs(loc0), SourceLocationIs(loc1), + SourceLocationIs(loc2))); +} + +TEST_F(StatusBuilderTest, EmptyGetPreviousSourceLocationsForNewFromStatusCode) { + const StatusBuilder builder(absl::StatusCode::kInvalidArgument); + EXPECT_THAT(builder.GetPreviousSourceLocations(), IsEmpty()); +} + +TEST_F(StatusBuilderTest, StatusCode) { + // OK + { + const StatusBuilder builder(absl::StatusCode::kOk); + EXPECT_TRUE(builder.ok()); + EXPECT_THAT(builder.code(), Eq(absl::StatusCode::kOk)); + } + // Non-OK code + { + const StatusBuilder builder(absl::StatusCode::kInvalidArgument); + EXPECT_FALSE(builder.ok()); + EXPECT_THAT(builder.code(), Eq(absl::StatusCode::kInvalidArgument)); + } +} + +TEST_F(StatusBuilderTest, OkIgnoresStuff) { + EXPECT_THAT(ToStatus(StatusBuilder(absl::OkStatus(), absl::SourceLocation()) + << "booyah"), + Eq(absl::OkStatus())); +} + +TEST_F(StatusBuilderTest, Streaming) { + EXPECT_THAT( + ToStatus(StatusBuilder(absl::CancelledError(), absl::SourceLocation()) + << "booyah"), + Eq(absl::CancelledError("booyah"))); + EXPECT_THAT( + ToStatus( + StatusBuilder(absl::AbortedError("hello"), absl::SourceLocation()) + << "world"), + Eq(absl::AbortedError("hello; world"))); +} + +TEST_F(StatusBuilderTest, PrependLvalue) { + { + StatusBuilder builder(absl::CancelledError(), absl::SourceLocation()); + EXPECT_THAT(ToStatus(builder.SetPrepend() << "booyah"), + Eq(absl::CancelledError("booyah"))); + } + { + StatusBuilder builder(absl::AbortedError(" hello"), absl::SourceLocation()); + EXPECT_THAT(ToStatus(builder.SetPrepend() << "world"), + Eq(absl::AbortedError("world hello"))); + } +} + +TEST_F(StatusBuilderTest, PrependRvalue) { + EXPECT_THAT( + ToStatus(StatusBuilder(absl::CancelledError(), absl::SourceLocation()) + .SetPrepend() + << "booyah"), + Eq(absl::CancelledError("booyah"))); + EXPECT_THAT(ToStatus(StatusBuilder(absl::AbortedError(" hello"), + absl::SourceLocation()) + .SetPrepend() + << "world"), + Eq(absl::AbortedError("world hello"))); +} + +TEST_F(StatusBuilderTest, AppendLvalue) { + { + StatusBuilder builder(absl::CancelledError(), absl::SourceLocation()); + EXPECT_THAT(ToStatus(builder.SetAppend() << "booyah"), + Eq(absl::CancelledError("booyah"))); + } + { + StatusBuilder builder(absl::AbortedError("hello"), absl::SourceLocation()); + EXPECT_THAT(ToStatus(builder.SetAppend() << " world"), + Eq(absl::AbortedError("hello world"))); + } +} + +TEST_F(StatusBuilderTest, AppendRvalue) { + EXPECT_THAT( + ToStatus(StatusBuilder(absl::CancelledError(), absl::SourceLocation()) + .SetAppend() + << "booyah"), + Eq(absl::CancelledError("booyah"))); + EXPECT_THAT(ToStatus(StatusBuilder(absl::AbortedError("hello"), + absl::SourceLocation()) + .SetAppend() + << " world"), + Eq(absl::AbortedError("hello world"))); +} + +TEST_F(StatusBuilderTest, WithRvalueRef) { + auto policy = [](StatusBuilder sb) { return sb << "policy"; }; + EXPECT_THAT(ToStatus(StatusBuilder(absl::AbortedError("hello"), + absl::SourceLocation()) + .With(policy)), + Eq(absl::AbortedError("hello; policy"))); +} + +TEST_F(StatusBuilderTest, WithRef) { + auto policy = [](StatusBuilder sb) { return sb << "policy"; }; + StatusBuilder sb(absl::AbortedError("zomg"), absl::SourceLocation()); + EXPECT_THAT(ToStatus(sb.With(policy)), + Eq(absl::AbortedError("zomg; policy"))); +} + +TEST_F(StatusBuilderTest, WithTypeChange) { + auto policy = [](StatusBuilder sb) -> std::string { + return sb.ok() ? "true" : "false"; + }; + EXPECT_EQ(StatusBuilder(absl::CancelledError(), absl::SourceLocation()) + .With(policy), + "false"); + EXPECT_EQ( + StatusBuilder(absl::OkStatus(), absl::SourceLocation()).With(policy), + "true"); +} + +struct MoveOnlyAdaptor { + std::unique_ptr<int> value; + std::unique_ptr<int> operator()(const absl::Status&) && { + return std::move(value); + } +}; + +TEST_F(StatusBuilderTest, WithMoveOnlyAdaptor) { + StatusBuilder sb(absl::AbortedError("zomg"), absl::SourceLocation()); + EXPECT_THAT(sb.With(MoveOnlyAdaptor{std::make_unique<int>(100)}), + Pointee(100)); + EXPECT_THAT(StatusBuilder(absl::AbortedError("zomg"), absl::SourceLocation()) + .With(MoveOnlyAdaptor{std::make_unique<int>(100)}), + Pointee(100)); +} + +struct StringifiableType { + absl::string_view message; + + template <typename Sink> + friend void AbslStringify(Sink& sink, const StringifiableType& o) { + sink.Append(o.message); + } +}; + +class MockLogSink : public absl::LogSink { + public: + MOCK_METHOD(void, Send, (const absl::LogEntry&), (override)); +}; + +TEST(WithExtraMessagePolicyTest, AppendsToExtraMessage) { + // The policy simply calls operator<< on the builder; the following examples + // demonstrate that, without duplicating all of the above tests. + EXPECT_THAT(ToStatus(StatusBuilder(absl::AbortedError("hello"), + absl::SourceLocation()) + .With(ExtraMessage("world"))), + Eq(absl::AbortedError("hello; world"))); + EXPECT_THAT(ToStatus(StatusBuilder(absl::AbortedError("hello"), + absl::SourceLocation()) + .With(ExtraMessage() << "world")), + Eq(absl::AbortedError("hello; world"))); + EXPECT_THAT(ToStatus(StatusBuilder(absl::AbortedError("hello"), + absl::SourceLocation()) + .With(ExtraMessage("world")) + .With(ExtraMessage("!"))), + Eq(absl::AbortedError("hello; world!"))); + EXPECT_THAT(ToStatus(StatusBuilder(absl::AbortedError("hello"), + absl::SourceLocation()) + .With(ExtraMessage("world, ")) + .SetPrepend()), + Eq(absl::AbortedError("world, hello"))); + EXPECT_THAT(ToStatus(StatusBuilder(absl::AbortedError("hello"), + absl::SourceLocation()) + .With(ExtraMessage() << StringifiableType{"world"})), + Eq(absl::AbortedError("hello; world"))); + + // The above examples use temporary StatusBuilder rvalues; verify things also + // work fine when StatusBuilder is an lvalue. + StatusBuilder builder(absl::AbortedError("hello"), absl::SourceLocation()); + EXPECT_THAT( + ToStatus(builder.With(ExtraMessage("world")).With(ExtraMessage("!"))), + Eq(absl::AbortedError("hello; world!"))); +} + +TEST(WithExtraMessagePolicyTest, + ExtraMessageStreamOperatorPreservesRvalueness) { + static_assert( + std::is_same_v<ExtraMessage&&, decltype(ExtraMessage() << "foo")>); +} + +TEST_F(StatusBuilderTest, StatusSourceLocationChaining) { + { + absl::Status src = absl::OkStatus(); + CheckSourceLocation(src); + CheckSourceLocation(ToStatus(StatusBuilder(src, absl::SourceLocation()))); + CheckSourceLocation( + ToStatus(StatusBuilder(src, absl::SourceLocation::current()))); + CheckSourceLocation( + ToStatus(StatusBuilder(src, absl::SourceLocation::current()) << "hmm")); + } + { + absl::Status src = absl::Status(absl::StatusCode::kCancelled, ""); + CheckSourceLocation(src); + CheckSourceLocation(ToStatus(StatusBuilder(src, absl::SourceLocation()))); + CheckSourceLocation( + ToStatus(StatusBuilder(src, absl::SourceLocation::current()))); + CheckSourceLocation( + ToStatus(StatusBuilder(src, absl::SourceLocation::current()) << "")); + CheckSourceLocation( + ToStatus(StatusBuilder(src, absl::SourceLocation::current()) << "hmm"), + {__builtin_LINE() - 1}); + } + { + absl::Status src = absl::Status(absl::StatusCode::kCancelled, "msg", + absl::SourceLocation()); + CheckSourceLocation(src); + CheckSourceLocation(ToStatus(StatusBuilder(src, absl::SourceLocation()))); + CheckSourceLocation( + ToStatus(StatusBuilder(src, absl::SourceLocation::current())), + {__builtin_LINE() - 1}); + CheckSourceLocation( + ToStatus(StatusBuilder(src, absl::SourceLocation::current()) << "hmm"), + {__builtin_LINE() - 1}); + } + { + absl::Status src = absl::Status(absl::StatusCode::kCancelled, "msg"); + int src_line = __builtin_LINE() - 1; + CheckSourceLocation(src, {src_line}); + CheckSourceLocation(ToStatus(StatusBuilder(src, absl::SourceLocation())), + {src_line}); + CheckSourceLocation( + ToStatus(StatusBuilder(src, absl::SourceLocation::current())), + {src_line, __builtin_LINE() - 1}); + CheckSourceLocation( + ToStatus(StatusBuilder(src, absl::SourceLocation::current()) << "hmm"), + {src_line, __builtin_LINE() - 1}); + } +} + +TEST_F(StatusBuilderTest, SetErrorCode) { + StatusBuilder builder; + builder.SetCode(absl::StatusCode::kResourceExhausted); + LOG(INFO) << "Builder code: " << builder; + EXPECT_FALSE(builder.ok()); + EXPECT_EQ(builder.code(), absl::StatusCode::kResourceExhausted); +} + +TEST_F(StatusBuilderTest, BuilderToStatusOrStatusShouldGiveErrorStatusOr) { + absl::StatusOr<absl::Status> value = StatusBuilder(absl::CancelledError()); + ASSERT_FALSE(value.ok()); + EXPECT_THAT(value.status(), StatusIs(absl::StatusCode::kCancelled)); +} + +} // namespace +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/status/status_macros.h b/absl/status/status_macros.h new file mode 100644 index 0000000..82e0dc7 --- /dev/null +++ b/absl/status/status_macros.h
@@ -0,0 +1,474 @@ +// Copyright 2026 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------- +// File: status_macros.h +// ----------------------------------------------------------------------------- +// +// Helper macros and methods to return and propagate errors with `absl::Status`. + +#ifndef ABSL_STATUS_STATUS_MACROS_H_ +#define ABSL_STATUS_STATUS_MACROS_H_ + +#include <cstddef> +#include <type_traits> +#include <utility> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/optimization.h" +#include "absl/status/status.h" +#include "absl/status/status_builder.h" // IWYU pragma: export +#include "absl/status/statusor.h" +#include "absl/types/source_location.h" + +// Evaluates an expression that produces a `absl::Status`. If the status is not +// ok, returns it from the current function. +// +// For example: +// absl::Status MultiStepFunction() { +// ABSL_RETURN_IF_ERROR(Function(args...)); +// ABSL_RETURN_IF_ERROR(foo.Method(args...)); +// return absl::OkStatus(); +// } +// +// The macro ends with a `absl::StatusBuilder` which allows the returned status +// to be extended with more details. Any chained expressions after the macro +// will not be evaluated unless there is an error. +// +// For example: +// absl::Status MultiStepFunction() { +// ABSL_RETURN_IF_ERROR(Function(args...)) << "in MultiStepFunction"; +// ABSL_RETURN_IF_ERROR(foo.Method(args...)).Log(absl::LogSeverity::kError) +// << "while processing query: " << query.DebugString(); +// return absl::OkStatus(); +// } +// +// `absl::StatusBuilder` supports adapting the builder chain using a `With` +// method and a functor. This allows for powerful extensions to the macro. +// +// For example, teams can define local policies to use across their code: +// +// StatusBuilder TeamPolicy(StatusBuilder builder) { +// return std::move(builder.Log(absl::LogSeverity::kWarning).Attach(...)); +// } +// +// ABSL_RETURN_IF_ERROR(foo()).With(TeamPolicy); +// ABSL_RETURN_IF_ERROR(bar()).With(TeamPolicy); +// +// Changing the return type allows the macro to be used with Task and Rpc +// interfaces. See `util::TaskReturn` and `rpc::RpcSetStatus` for details. +// +// void Read(StringPiece name, util::Task* task) { +// int64 id; +// ABSL_RETURN_IF_ERROR(GetIdForName(name, &id)).With(TaskReturn(task)); +// ABSL_RETURN_IF_ERROR(ReadForId(id)).With(TaskReturn(task)); +// task->Return(); +// } +// +// If using this macro inside a lambda, you need to annotate the return type +// to avoid confusion between a `absl::StatusBuilder` and a `absl::Status` type. +// E.g. +// +// []() -> absl::Status { +// ABSL_RETURN_IF_ERROR(Function(args...)); +// ABSL_RETURN_IF_ERROR(foo.Method(args...)); +// return absl::OkStatus(); +// } +#define ABSL_RETURN_IF_ERROR(expr) \ + ABSL_INTERNAL_STATUS_MACROS_RETURN_IF_ERROR_IMPL_(return, expr) + +// Executes an expression `rexpr` that returns an `absl::StatusOr<T>`. On OK, +// moves its value into the variable defined by `lhs`, otherwise returns +// from the current function. By default the error status is returned +// unchanged, but it may be modified by an `error_expression`. If there is an +// error, `lhs` is not evaluated; thus any side effects that `lhs` may have +// only occur in the success case. +// +// Interface: +// +// ABSL_ASSIGN_OR_RETURN(lhs, rexpr) +// ABSL_ASSIGN_OR_RETURN(lhs, rexpr, error_expression); +// +// WARNING: if lhs is parenthesized, the parentheses are removed. See examples +// for more details. +// +// WARNING: expands into multiple statements; it cannot be used in a single +// statement (e.g. as the body of an if statement without {})! +// +// Example: Declaring and initializing a new variable (ValueType can be anything +// that can be initialized with assignment--including a const +// reference, although that's discouraged by go/totw/107): +// ABSL_ASSIGN_OR_RETURN(ValueType value, MaybeGetValue(arg)); +// +// Example: Assigning to an existing variable: +// ValueType value; +// ABSL_ASSIGN_OR_RETURN(value, MaybeGetValue(arg)); +// +// Example: Assigning to an expression with side effects: +// MyProto data; +// ABSL_ASSIGN_OR_RETURN(*data.mutable_str(), MaybeGetValue(arg)); +// // No field "str" is added on error. +// +// Example: Initializing a `std::unique_ptr`. +// ABSL_ASSIGN_OR_RETURN(std::unique_ptr<T> ptr, MaybeGetPtr(arg)); +// +// Example: Initializing a map. Because of C++ preprocessor limitations, +// the type used in ABSL_ASSIGN_OR_RETURN cannot contain commas, so wrap the +// lhs in parentheses: +// ABSL_ASSIGN_OR_RETURN((absl::flat_hash_map<Foo, Bar> my_map), GetMap()); +// Or use `auto` if the type is obvious enough: +// ABSL_ASSIGN_OR_RETURN(auto my_map, GetMap()); +// +// Example: Assigning to structured bindings (go/totw/169). The same situation +// with comma as in map, so wrap the statement in parentheses. +// ABSL_ASSIGN_OR_RETURN((auto [first, second]), GetPair()); +// +// If passed, the `error_expression` is evaluated to produce the return +// value. The expression may reference any variable visible in scope, as +// well as a `absl::StatusBuilder` object populated with the error and +// named by a single underscore `_`. The expression typically uses the +// builder to modify the status and is returned directly in manner similar +// to ABSL_RETURN_IF_ERROR. The expression may, however, evaluate to any type +// returnable by the function, including (void). For example: +// +// Example: Adjusting the error message. +// ABSL_ASSIGN_OR_RETURN(ValueType value, MaybeGetValue(query), +// _ << "while processing query " << query.DebugString()); +// +// Example: Logging the error on failure. +// ABSL_ASSIGN_OR_RETURN(ValueType value, MaybeGetValue(query), _.LogError()); +// +#define ABSL_ASSIGN_OR_RETURN(...) \ + ABSL_INTERNAL_STATUS_MACROS_ASSIGN_OR_RETURN_IMPL_(return, __VA_ARGS__) + +// ================================================================= +// == Implementation details, do not rely on anything below here. == +// ================================================================= + +#define ABSL_INTERNAL_STATUS_MACROS_RETURN_IF_ERROR_IMPL_(return_keyword, \ + expr) \ + ABSL_INTERNAL_STATUS_MACROS_IMPL_ELSE_BLOCKER_ \ + if (auto status_macro_internal_adaptor = \ + absl::status_macro_internal::MacroAdaptor( \ + (expr), absl::SourceLocation::current())) { \ + } else /* NOLINT */ \ + return_keyword status_macro_internal_adaptor.Consume() + +#define ABSL_INTERNAL_STATUS_MACROS_ASSIGN_OR_RETURN_IMPL_(return_keyword, \ + ...) \ + ABSL_INTERNAL_STATUS_MACROS_IMPL_GET_VARIADIC_( \ + (return_keyword, __VA_ARGS__, \ + ABSL_INTERNAL_STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_3_, \ + ABSL_INTERNAL_STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_2_)) \ + (return_keyword, __VA_ARGS__) + +constexpr bool HasPotentialConditionalOperator(const char* lhs, int size) { + for (int i = 0; i < size; ++i) { + if (lhs[i] == '?') { + return true; + } + } + return false; +} + +template <std::size_t N> +constexpr bool IsEnclosedByParentheses(const char (&lhs)[N]) { + if (N < 2) { + return false; + } + return lhs[0] == '(' && lhs[N - 2] == ')'; +} + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace status_macro_internal { + +template <typename T, typename EnableIf = void> +struct IsAllowedStatusOrMacroType : std::false_type {}; + +template <typename T> +struct IsAllowedStatusOrMacroType< + T, std::enable_if_t<std::is_convertible_v< + T*, typename absl::StatusOr<typename T::value_type>*>>> + : std::true_type {}; + +} // namespace status_macro_internal +ABSL_NAMESPACE_END +} // namespace absl + +// MSVC incorrectly expands variadic macros, splice together a macro call to +// work around the bug. +#define ABSL_INTERNAL_STATUS_MACROS_IMPL_GET_VARIADIC_HELPER_(_1, _2, _3, _4, \ + NAME, ...) \ + NAME +#define ABSL_INTERNAL_STATUS_MACROS_IMPL_GET_VARIADIC_(args) \ + /* NOLINTNEXTLINE(clang-diagnostic-pre-c++20-compat) */ \ + ABSL_INTERNAL_STATUS_MACROS_IMPL_GET_VARIADIC_HELPER_ args + +#define ABSL_INTERNAL_STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_2_(return_keyword, \ + lhs, rexpr) \ + ABSL_INTERNAL_STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_( \ + ABSL_INTERNAL_STATUS_MACROS_IMPL_CONCAT_(_status_or_value, __LINE__), \ + lhs, rexpr, \ + return_keyword absl::Status( \ + std::move(ABSL_INTERNAL_STATUS_MACROS_IMPL_CONCAT_(_status_or_value, \ + __LINE__)) \ + .status(), \ + absl::SourceLocation::current())) + +#define ABSL_INTERNAL_STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_3_( \ + return_keyword, lhs, rexpr, error_expression) \ + ABSL_INTERNAL_STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_( \ + ABSL_INTERNAL_STATUS_MACROS_IMPL_CONCAT_(_status_or_value, __LINE__), \ + lhs, rexpr, /* NOLINTNEXTLINE(misc-const-correctness) */ \ + absl::StatusBuilder _( \ + std::move(ABSL_INTERNAL_STATUS_MACROS_IMPL_CONCAT_(_status_or_value, \ + __LINE__)) \ + .status(), \ + absl::SourceLocation::current()); \ + (void)_; /* error_expression is allowed to not use this variable */ \ + return_keyword(error_expression)) + +#define ABSL_INTERNAL_STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_( \ + statusor, lhs, rexpr, error_expression) \ + auto statusor = (rexpr); \ + if (ABSL_PREDICT_FALSE(!statusor.ok())) { \ + error_expression; \ + } \ + { \ + static_assert( \ + !IsEnclosedByParentheses(#lhs) || \ + !HasPotentialConditionalOperator(#lhs, sizeof(#lhs) - 2), \ + "Identified potential conditional operator, consider not " \ + "using ABSL_ASSIGN_OR_RETURN"); \ + } \ + { \ + static_assert( \ + absl::status_macro_internal::IsAllowedStatusOrMacroType< \ + typename std::remove_const<decltype(statusor)>::type>(), \ + "ABSL_ASSIGN_OR_RETURN should only be used with absl::StatusOr<>"); \ + } \ + ABSL_INTERNAL_STATUS_MACROS_IMPL_UNPARENTHESIZE_IF_PARENTHESIZED(lhs) = \ + (*std::move(statusor)) + +// Internal helpers for macro expansion. +#define ABSL_INTERNAL_STATUS_MACROS_IMPL_EAT(...) +#define ABSL_INTERNAL_STATUS_MACROS_IMPL_REM(...) __VA_ARGS__ +#define ABSL_INTERNAL_STATUS_MACROS_IMPL_EMPTY() + +// Internal helpers for emptiness arguments check. +#define ABSL_INTERNAL_STATUS_MACROS_IMPL_IS_EMPTY_INNER(...) \ + ABSL_INTERNAL_STATUS_MACROS_IMPL_IS_EMPTY_INNER_HELPER((__VA_ARGS__, 0, 1)) +// MSVC expands variadic macros incorrectly, so we need this extra indirection +// to work around that (b/110959038). +#define ABSL_INTERNAL_STATUS_MACROS_IMPL_IS_EMPTY_INNER_HELPER(args) \ + ABSL_INTERNAL_STATUS_MACROS_IMPL_IS_EMPTY_INNER_I args +#define ABSL_INTERNAL_STATUS_MACROS_IMPL_IS_EMPTY_INNER_I(e0, e1, is_empty, \ + ...) \ + is_empty + +#define ABSL_INTERNAL_STATUS_MACROS_IMPL_IS_EMPTY(...) \ + ABSL_INTERNAL_STATUS_MACROS_IMPL_IS_EMPTY_I(__VA_ARGS__) + +#if !defined(_MSC_VER) || defined(__clang__) || \ + (defined(_MSVC_TRADITIONAL) && !_MSVC_TRADITIONAL) +#define ABSL_INTERNAL_STATUS_MACROS_IMPL_IS_EMPTY_I(...) \ + ABSL_INTERNAL_STATUS_MACROS_IMPL_IS_EMPTY_INNER(_ __VA_OPT__(, )##__VA_ARGS__) +#else +// Nonstandard hack that works for MSVC's legacy preprocessor +#define ABSL_INTERNAL_STATUS_MACROS_IMPL_IS_EMPTY_I(...) \ + ABSL_INTERNAL_STATUS_MACROS_IMPL_IS_EMPTY_INNER(_, ##__VA_ARGS__) +#endif + +// Internal helpers for if statement. +#define ABSL_INTERNAL_STATUS_MACROS_IMPL_IF_1(_Then, _Else) _Then +#define ABSL_INTERNAL_STATUS_MACROS_IMPL_IF_0(_Then, _Else) _Else +#define ABSL_INTERNAL_STATUS_MACROS_IMPL_IF(_Cond, _Then, _Else) \ + ABSL_INTERNAL_STATUS_MACROS_IMPL_CONCAT_( \ + ABSL_INTERNAL_STATUS_MACROS_IMPL_IF_, _Cond)(_Then, _Else) + +// Expands to 1 if the input is parenthesized. Otherwise expands to 0. +#define ABSL_INTERNAL_STATUS_MACROS_IMPL_IS_PARENTHESIZED(...) \ + ABSL_INTERNAL_STATUS_MACROS_IMPL_IS_EMPTY( \ + ABSL_INTERNAL_STATUS_MACROS_IMPL_EAT __VA_ARGS__) + +// If the input is parenthesized, removes the parentheses. Otherwise expands to +// the input unchanged. +#define ABSL_INTERNAL_STATUS_MACROS_IMPL_UNPARENTHESIZE_IF_PARENTHESIZED(...) \ + ABSL_INTERNAL_STATUS_MACROS_IMPL_IF( \ + ABSL_INTERNAL_STATUS_MACROS_IMPL_IS_PARENTHESIZED(__VA_ARGS__), \ + ABSL_INTERNAL_STATUS_MACROS_IMPL_REM, \ + ABSL_INTERNAL_STATUS_MACROS_IMPL_EMPTY()) \ + __VA_ARGS__ + +// Internal helper for concatenating macro values. +#define ABSL_INTERNAL_STATUS_MACROS_IMPL_CONCAT_INNER_(x, y) x##y + +#define ABSL_INTERNAL_STATUS_MACROS_IMPL_CONCAT_(x, y) \ + ABSL_INTERNAL_STATUS_MACROS_IMPL_CONCAT_INNER_(x, y) + +// The GNU compiler emits a warning for code like: +// +// if (foo) +// if (bar) { } else baz; +// +// because it thinks you might want the else to bind to the first if. This +// leads to problems with code like: +// +// if (do_expr) ABSL_RETURN_IF_ERROR(expr) << "Some message"; +// +// The "switch (0) case 0:" idiom is used to suppress this. +// TODO(b/491833032): Remove this once all users and redefinitions are updated. +#define STATUS_MACROS_IMPL_ELSE_BLOCKER_ \ + switch (0) \ + case 0: \ + default: // NOLINT +#define ABSL_INTERNAL_STATUS_MACROS_IMPL_ELSE_BLOCKER_ \ + switch (0) \ + case 0: \ + default: // NOLINT + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace status_macro_internal { + +// Provides a conversion to bool so that it can be used inside an if statement +// that declares a variable. +class StatusAdaptorForMacros { + public: + StatusAdaptorForMacros( + const absl::Status& status, + absl::SourceLocation loc = absl::SourceLocation::current()) + : builder_(status, loc) {} + + StatusAdaptorForMacros( + absl::Status&& status, + absl::SourceLocation loc = absl::SourceLocation::current()) + : builder_(std::move(status), loc) {} + + StatusAdaptorForMacros(const StatusBuilder& builder, + absl::SourceLocation = absl::SourceLocation()) + : builder_(builder) {} + + StatusAdaptorForMacros(StatusBuilder&& builder, + absl::SourceLocation = absl::SourceLocation()) + : builder_(std::move(builder)) {} + + StatusAdaptorForMacros(const StatusAdaptorForMacros&) = delete; + StatusAdaptorForMacros& operator=(const StatusAdaptorForMacros&) = delete; + + explicit operator bool() const { return ABSL_PREDICT_TRUE(builder_.ok()); } + + StatusBuilder&& Consume() { return std::move(builder_); } + + private: + StatusBuilder builder_; +}; + +// Special adaptor for use by ABSL_RETURN_IF_ERROR for absl::Status arguments. +// This one avoids constructing a StatusBuilder on the fast path. +// +// REQUIRES: Only used by ABSL_RETURN_IF_ERROR implementation. +class ReturnIfErrorAdaptor { + public: + explicit ReturnIfErrorAdaptor( + const absl::Status& status, + absl::SourceLocation loc = absl::SourceLocation::current()) + : status_(status), loc_(loc) {} + + explicit ReturnIfErrorAdaptor( + absl::Status&& status, + absl::SourceLocation loc = absl::SourceLocation::current()) + : status_(std::move(status)), loc_(loc) {} + + ~ReturnIfErrorAdaptor() { + // WARNING! WARNING! WARNING! + // + // We play fast and loose here and avoid destroying status_. This should be + // safe because status_ will never own memory at destruction time. The two + // cases to consider are: + // (1) OK: OkStatus() representation needs no cleanup + // (2) Not-OK: we take the else branch in ABSL_RETURN_IF_ERROR and move + // status_ into StatusBuilder which leaves status_ with a MovedFromRep + // that needs no cleanup. + // If the absl::Status implementation changes, leaks should be caught by + // the various tests we have when run under lsan or debug leak checker. + } + + explicit operator bool() const { return ABSL_PREDICT_TRUE(status_.ok()); } + + StatusBuilder Consume() { return StatusBuilder(std::move(status_), loc_); } + + private: + // Place the status inside a union so we can avoid generating unnecessary code + // to call the Status destructor. + union { + absl::Status status_; + char nothing_[1]; + }; + absl::SourceLocation loc_; +}; + +// Overloads of MacroAdaptor that pick the right adaptor class +// for each argument type. +// +// TODO(b/501387693): Replace these with deduction guides of template +// specializations once the bug is fixed. (See prior file revision.) +inline ReturnIfErrorAdaptor MacroAdaptor(const absl::Status& s, + absl::SourceLocation loc) { + return ReturnIfErrorAdaptor(s, loc); +} +inline ReturnIfErrorAdaptor MacroAdaptor(absl::Status&& s, + absl::SourceLocation loc) { + return ReturnIfErrorAdaptor(std::move(s), loc); +} +inline StatusAdaptorForMacros MacroAdaptor(const StatusBuilder& s, + absl::SourceLocation loc) { + return StatusAdaptorForMacros(s, loc); +} +inline StatusAdaptorForMacros MacroAdaptor(StatusBuilder&& s, + absl::SourceLocation loc) { + return StatusAdaptorForMacros(std::move(s), loc); +} + +} // namespace status_macro_internal + +// By defining ABSL_DEFINE_UNQUALIFIED_STATUS_MACROS, this library also provides +// unqualified versions of its macros. +// +// Unqualified macro names are likely to collide with those in other projects, +// and so are not recommended. Furthermore, this is true for any transitive +// dependencies of Abseil: it is impossible to be confident that downstream +// libraries will neither define these macros themselves nor depend on a +// different library that also defines them. +// +// To enable unqualified names despite the caveats, define +// `ABSL_DEFINE_UNQUALIFIED_STATUS_MACROS` preferably at the command line, e.g. +// `-DABSL_DEFINE_UNQUALIFIED_STATUS_MACROS`, or `local_defines = +// ["ABSL_DEFINE_UNQUALIFIED_STATUS_MACROS"]` if using Bazel. +// +// These are turned on by default inside Google's internal codebase where their +// use is historically ubiquitous. Other OSS Google projects should use the +// qualified versions. +// +#ifdef ABSL_DEFINE_UNQUALIFIED_STATUS_MACROS +#define ASSIGN_OR_RETURN(...) ABSL_ASSIGN_OR_RETURN(__VA_ARGS__) +#define RETURN_IF_ERROR(...) ABSL_RETURN_IF_ERROR(__VA_ARGS__) +#endif // ABSL_DEFINE_UNQUALIFIED_STATUS_MACROS + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_STATUS_STATUS_MACROS_H_
diff --git a/absl/status/status_macros_test.cc b/absl/status/status_macros_test.cc new file mode 100644 index 0000000..7aaeb43 --- /dev/null +++ b/absl/status/status_macros_test.cc
@@ -0,0 +1,650 @@ +// Copyright 2026 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/status/status_macros.h" + +#include <cstddef> +#include <memory> +#include <string> +#include <tuple> +#include <type_traits> +#include <utility> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/attributes.h" +#include "absl/base/optimization.h" +#include "absl/status/status.h" +#include "absl/status/status_builder.h" +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "absl/types/source_location.h" + +namespace { + +using ::testing::AllOf; +using ::testing::Eq; +using ::testing::HasSubstr; + +using absl::status_macro_internal::ReturnIfErrorAdaptor; +using absl::status_macro_internal::StatusAdaptorForMacros; +static_assert(!std::is_default_constructible_v<ReturnIfErrorAdaptor>); +static_assert(!std::is_default_constructible_v<StatusAdaptorForMacros>); +static_assert(!std::is_copy_constructible_v<ReturnIfErrorAdaptor>); +static_assert(!std::is_copy_constructible_v<StatusAdaptorForMacros>); +static_assert(!std::is_copy_assignable_v<ReturnIfErrorAdaptor>); +static_assert(!std::is_copy_assignable_v<StatusAdaptorForMacros>); + +absl::Status ReturnOk() { return absl::OkStatus(); } + +absl::StatusBuilder ReturnOkBuilder() { + return absl::StatusBuilder(absl::OkStatus()); +} + +absl::Status ReturnError(absl::string_view msg) { + return absl::Status(absl::StatusCode::kUnknown, msg); +} + +absl::StatusBuilder ReturnErrorBuilder(absl::string_view msg) { + return absl::StatusBuilder(absl::Status(absl::StatusCode::kUnknown, msg)); +} + +absl::StatusOr<int> ReturnStatusOrValue(int v) { return v; } + +absl::StatusOr<int> ReturnStatusOrError(absl::string_view msg) { + return absl::Status(absl::StatusCode::kUnknown, msg); +} + +template <class... Args> +absl::StatusOr<std::tuple<Args...>> ReturnStatusOrTupleValue(Args&&... v) { + return std::tuple<Args...>(std::forward<Args>(v)...); +} + +template <class... Args> +absl::StatusOr<std::tuple<Args...>> ReturnStatusOrTupleError( + absl::string_view msg) { + return absl::Status(absl::StatusCode::kUnknown, msg); +} + +absl::StatusOr<int&> ReturnStatusOrRef(int& v) { return v; } + +absl::StatusOr<std::unique_ptr<int>> ReturnStatusOrPtrValue(int v) { + return std::make_unique<int>(v); +} +void CheckSourceLocation( + const absl::Status& status, std::vector<int> lines = {}, + absl::SourceLocation loc = absl::SourceLocation::current()) { + ASSERT_EQ(status.GetSourceLocations().size(), lines.size()) + << "Size check failed at " << loc.line(); + for (size_t i = 0; i < lines.size(); ++i) { + EXPECT_EQ(absl::string_view(status.GetSourceLocations()[i].file_name()), + absl::string_view(loc.file_name())) + << "File name check failed at " << loc.line(); + EXPECT_EQ(status.GetSourceLocations()[i].line(), lines[i]) + << "Line check failed at " << loc.line(); + } +} +TEST(AssignOrReturn, Works) { + auto func = []() -> absl::Status { + ABSL_ASSIGN_OR_RETURN(int value1, ReturnStatusOrValue(1)); + EXPECT_EQ(1, value1); + ABSL_ASSIGN_OR_RETURN(const int value2, ReturnStatusOrValue(2)); + EXPECT_EQ(2, value2); + ABSL_ASSIGN_OR_RETURN(const int& value3, ReturnStatusOrValue(3)); + EXPECT_EQ(3, value3); + ABSL_ASSIGN_OR_RETURN(int value4 [[maybe_unused]], + ReturnStatusOrError("EXPECTED")); + return ReturnError("ERROR"); + }; + + EXPECT_THAT(func().message(), Eq("EXPECTED")); +} + +TEST(AssignOrReturn, WorksWithReferences) { + int value = 17; + auto func = [&]() -> absl::Status { + ABSL_ASSIGN_OR_RETURN(int& value1, ReturnStatusOrRef(value)); + EXPECT_EQ(&value1, &value); + + ABSL_ASSIGN_OR_RETURN(const int& value2, ReturnStatusOrRef(value)); + EXPECT_EQ(&value2, &value); + + ABSL_ASSIGN_OR_RETURN(int value3, ReturnStatusOrRef(value)); + EXPECT_EQ(value3, value); + + value = 11; + EXPECT_NE(value3, value); + + ABSL_ASSIGN_OR_RETURN(int value4 [[maybe_unused]], + ReturnStatusOrError("EXPECTED")); + return ReturnError("ERROR"); + }; + + EXPECT_THAT(func().message(), Eq("EXPECTED")); +} + +TEST(AssignOrReturn, WorksWithCommasInType) { +#if defined(_MSC_VER) && (!defined(_MSVC_TRADITIONAL) || _MSVC_TRADITIONAL) + GTEST_SKIP() << "Comma support on MSVC requires /Zc:preprocessor"; +#else + auto func = []() -> absl::Status { + ABSL_ASSIGN_OR_RETURN((std::tuple<int, int> t1), + ReturnStatusOrTupleValue(1, 1)); + EXPECT_EQ((std::tuple{1, 1}), t1); + ABSL_ASSIGN_OR_RETURN((const std::tuple<int, std::tuple<int, int>, int> t2), + ReturnStatusOrTupleValue(1, std::tuple{1, 1}, 1)); + EXPECT_EQ((std::tuple{1, std::tuple{1, 1}, 1}), t2); + ABSL_ASSIGN_OR_RETURN( + (std::tuple<int, std::tuple<int, int>, int> t3), + (ReturnStatusOrTupleError<int, std::tuple<int, int>, int>("EXPECTED"))); + t3 = {}; // fix unused error + return ReturnError("ERROR"); + }; + + EXPECT_THAT(func().message(), Eq("EXPECTED")); +#endif +} + +TEST(AssignOrReturn, WorksWithStructureBindings) { +#if defined(_MSC_VER) && (!defined(_MSVC_TRADITIONAL) || _MSVC_TRADITIONAL) + GTEST_SKIP() << "Comma support on MSVC requires /Zc:preprocessor"; +#else + auto func = []() -> absl::Status { + ABSL_ASSIGN_OR_RETURN( + (const auto& [t1, t2, t3, t4, t5]), + ReturnStatusOrTupleValue(std::tuple{1, 1}, 1, 2, 3, 4)); + EXPECT_EQ((std::tuple{1, 1}), t1); + EXPECT_EQ(1, t2); + EXPECT_EQ(2, t3); + EXPECT_EQ(3, t4); + EXPECT_EQ(4, t5); + ABSL_ASSIGN_OR_RETURN(int t6 [[maybe_unused]], + ReturnStatusOrError("EXPECTED")); + return ReturnError("ERROR"); + }; + + EXPECT_THAT(func().message(), Eq("EXPECTED")); +#endif +} + +TEST(AssignOrReturn, WorksWithParenthesesAndDereference) { +#if defined(_MSC_VER) && (!defined(_MSVC_TRADITIONAL) || _MSVC_TRADITIONAL) + GTEST_SKIP() << "Comma support on MSVC requires /Zc:preprocessor"; +#else + auto func = []() -> absl::Status { + int integer; + int* pointer_to_integer = &integer; + ABSL_ASSIGN_OR_RETURN((*pointer_to_integer), ReturnStatusOrValue(1)); + EXPECT_EQ(1, integer); + ABSL_ASSIGN_OR_RETURN(*pointer_to_integer, ReturnStatusOrValue(2)); + EXPECT_EQ(2, integer); + // Make the test where the order of dereference matters and treat the + // parentheses. + pointer_to_integer--; + int** pointer_to_pointer_to_integer = &pointer_to_integer; + ABSL_ASSIGN_OR_RETURN((*pointer_to_pointer_to_integer)[1], + ReturnStatusOrValue(3)); + EXPECT_EQ(3, integer); + ABSL_ASSIGN_OR_RETURN(int t1 [[maybe_unused]], + ReturnStatusOrError("EXPECTED")); + return ReturnError("ERROR"); + }; + + EXPECT_THAT(func().message(), Eq("EXPECTED")); +#endif +} + +TEST(AssignOrReturn, WorksWithAppend) { + auto fail_test_if_called = []() -> std::string { + ADD_FAILURE(); + return "FAILURE"; + }; + auto func = [&]() -> absl::Status { + int value [[maybe_unused]]; + ABSL_ASSIGN_OR_RETURN(value, ReturnStatusOrValue(1), + _ << fail_test_if_called()); + ABSL_ASSIGN_OR_RETURN(value, ReturnStatusOrError("EXPECTED A"), + _ << "EXPECTED B"); + return ReturnOk(); + }; + + EXPECT_THAT(func().message(), + AllOf(HasSubstr("EXPECTED A"), HasSubstr("EXPECTED B"))); +} + +TEST(AssignOrReturn, WorksWithAdaptorFunc) { + auto fail_test_if_called = [](absl::StatusBuilder builder) { + ADD_FAILURE(); + return builder; + }; + auto adaptor = [](absl::StatusBuilder builder) { + return builder << "EXPECTED B"; + }; + auto func = [&]() -> absl::Status { + int value [[maybe_unused]]; + ABSL_ASSIGN_OR_RETURN(value, ReturnStatusOrValue(1), + fail_test_if_called(_)); + ABSL_ASSIGN_OR_RETURN(value, ReturnStatusOrError("EXPECTED A"), adaptor(_)); + return ReturnOk(); + }; + + EXPECT_THAT(func().message(), + AllOf(HasSubstr("EXPECTED A"), HasSubstr("EXPECTED B"))); +} + +TEST(AssignOrReturn, WorksWithThirdArgumentAndCommas) { +#if defined(_MSC_VER) && (!defined(_MSVC_TRADITIONAL) || _MSVC_TRADITIONAL) + GTEST_SKIP() << "Comma support on MSVC requires /Zc:preprocessor"; +#else + auto fail_test_if_called = [](absl::StatusBuilder builder) { + ADD_FAILURE(); + return builder; + }; + auto adaptor = [](absl::StatusBuilder builder) { + return builder << "EXPECTED B"; + }; + auto func = [&]() -> absl::Status { + ABSL_ASSIGN_OR_RETURN((const auto& [t1, t2, t3]), + ReturnStatusOrTupleValue(1, 2, 3), + fail_test_if_called(_)); + EXPECT_EQ(t1, 1); + EXPECT_EQ(t2, 2); + EXPECT_EQ(t3, 3); + ABSL_ASSIGN_OR_RETURN( + (const auto& [t4, t5, t6]), + (ReturnStatusOrTupleError<int, int, int>("EXPECTED A")), adaptor(_)); + // Silence errors about the unused values. + static_cast<void>(t4); + static_cast<void>(t5); + static_cast<void>(t6); + return ReturnOk(); + }; + + EXPECT_THAT(func().message(), + AllOf(HasSubstr("EXPECTED A"), HasSubstr("EXPECTED B"))); +#endif +} + +TEST(AssignOrReturn, WorksWithAppendIncludingLocals) { + auto func = [&](absl::string_view str) -> absl::Status { + int value [[maybe_unused]]; + ABSL_ASSIGN_OR_RETURN(value, ReturnStatusOrError("EXPECTED A"), _ << str); + return ReturnOk(); + }; + + EXPECT_THAT(func("EXPECTED B").message(), + AllOf(HasSubstr("EXPECTED A"), HasSubstr("EXPECTED B"))); +} + +TEST(AssignOrReturn, WorksForExistingVariable) { + auto func = []() -> absl::Status { + int value = 1; + ABSL_ASSIGN_OR_RETURN(value, ReturnStatusOrValue(2)); + EXPECT_EQ(2, value); + ABSL_ASSIGN_OR_RETURN(value, ReturnStatusOrValue(3)); + EXPECT_EQ(3, value); + ABSL_ASSIGN_OR_RETURN(value, ReturnStatusOrError("EXPECTED")); + return ReturnError("ERROR"); + }; + + EXPECT_THAT(func().message(), Eq("EXPECTED")); +} + +TEST(AssignOrReturn, UniquePtrWorks) { + auto func = []() -> absl::Status { + ABSL_ASSIGN_OR_RETURN(std::unique_ptr<int> ptr, ReturnStatusOrPtrValue(1)); + EXPECT_EQ(*ptr, 1); + return ReturnError("EXPECTED"); + }; + + EXPECT_THAT(func().message(), Eq("EXPECTED")); +} + +TEST(AssignOrReturn, UniquePtrWorksForExistingVariable) { + auto func = []() -> absl::Status { + std::unique_ptr<int> ptr; + ABSL_ASSIGN_OR_RETURN(ptr, ReturnStatusOrPtrValue(1)); + EXPECT_EQ(*ptr, 1); + + ABSL_ASSIGN_OR_RETURN(ptr, ReturnStatusOrPtrValue(2)); + EXPECT_EQ(*ptr, 2); + return ReturnError("EXPECTED"); + }; + + EXPECT_THAT(func().message(), Eq("EXPECTED")); +} + +TEST(AssignOrReturn, ChainSourceLocation) { + auto func1 = []() -> absl::StatusOr<std::unique_ptr<int>> { + std::unique_ptr<int> ptr; + ABSL_ASSIGN_OR_RETURN(ptr, ReturnStatusOrPtrValue(1)); + return ptr; + }; + auto func2 = []() -> absl::StatusOr<std::unique_ptr<int>> { + return absl::Status(absl::StatusCode::kInternal, "msg"); + }; + int func2_line = __builtin_LINE() - 2; + + auto func3 = [=]() -> absl::StatusOr<std::unique_ptr<int>> { + std::unique_ptr<int> ptr; + ABSL_ASSIGN_OR_RETURN(ptr, func1()); + ABSL_ASSIGN_OR_RETURN(ptr, func2()); + return ptr; + }; + int func3_line = __builtin_LINE() - 3; + + auto func4 = [=]() -> absl::StatusOr<std::unique_ptr<int>> { + std::unique_ptr<int> ptr; + ABSL_ASSIGN_OR_RETURN(ptr, func3()); + ABSL_ASSIGN_OR_RETURN(ptr, func2()); + return ptr; + }; + int func4_line = __builtin_LINE() - 4; + + absl::StatusOr<std::unique_ptr<int>> result = func4(); + EXPECT_EQ(absl::StatusCode::kInternal, result.status().code()); + CheckSourceLocation(result.status(), {func2_line, func3_line, func4_line}); +} + +TEST(AssignOrReturn, NotChainSourceLocationWithEmptyMsg) { + auto func1 = []() -> absl::StatusOr<std::unique_ptr<int>> { + std::unique_ptr<int> ptr; + ABSL_ASSIGN_OR_RETURN(ptr, ReturnStatusOrPtrValue(1)); + return ptr; + }; + auto func2 = []() -> absl::StatusOr<std::unique_ptr<int>> { + return absl::Status(absl::StatusCode::kInternal, ""); + }; + + auto func3 = [=]() -> absl::StatusOr<std::unique_ptr<int>> { + std::unique_ptr<int> ptr; + ABSL_ASSIGN_OR_RETURN(ptr, func1()); + ABSL_ASSIGN_OR_RETURN(ptr, func2()); + return ptr; + }; + + auto func4 = [=]() -> absl::StatusOr<std::unique_ptr<int>> { + std::unique_ptr<int> ptr; + ABSL_ASSIGN_OR_RETURN(ptr, func3()); + ABSL_ASSIGN_OR_RETURN(ptr, func2()); + return ptr; + }; + + absl::StatusOr<std::unique_ptr<int>> result = func4(); + EXPECT_EQ(absl::StatusCode::kInternal, result.status().code()); + CheckSourceLocation(result.status()); +} + +TEST(AssignOrReturn, ChainSourceLocationWith3ArgStatusMacro) { + auto func1 = []() -> absl::StatusOr<std::unique_ptr<int>> { + std::unique_ptr<int> ptr; + ABSL_ASSIGN_OR_RETURN(ptr, ReturnStatusOrPtrValue(1)); + return ptr; + }; + auto func2 = []() -> absl::StatusOr<std::unique_ptr<int>> { + return absl::Status(absl::StatusCode::kInternal, ""); + }; + + auto func3 = [=]() -> absl::StatusOr<std::unique_ptr<int>> { + std::unique_ptr<int> ptr; + ABSL_ASSIGN_OR_RETURN(ptr, func1()); + ABSL_ASSIGN_OR_RETURN(ptr, func2(), _ << "hmm"); + return ptr; + }; + int func3_line = __builtin_LINE() - 3; + + auto func4 = [=]() -> absl::StatusOr<std::unique_ptr<int>> { + std::unique_ptr<int> ptr; + ABSL_ASSIGN_OR_RETURN(ptr, func3()); + ABSL_ASSIGN_OR_RETURN(ptr, func2()); + return ptr; + }; + int func4_line = __builtin_LINE() - 4; + + absl::StatusOr<std::unique_ptr<int>> result = func4(); + EXPECT_EQ(absl::StatusCode::kInternal, result.status().code()); + CheckSourceLocation(result.status(), {func3_line, func4_line}); +} + +TEST(ReturnIfError, Works) { + auto func = []() -> absl::Status { + ABSL_RETURN_IF_ERROR(ReturnOk()); + ABSL_RETURN_IF_ERROR(ReturnOk()); + ABSL_RETURN_IF_ERROR(ReturnError("EXPECTED")); + return ReturnError("ERROR"); + }; + + EXPECT_THAT(func().message(), Eq("EXPECTED")); +} + +TEST(ReturnIfError, WorksWithSourceLocation) { + using StatusCode = absl::StatusCode; + { + auto func = []() -> absl::Status { + ABSL_RETURN_IF_ERROR(ReturnOk()); + ABSL_RETURN_IF_ERROR(ReturnOk()); + ABSL_RETURN_IF_ERROR(absl::Status(StatusCode::kUnknown, "EXPECTED")); + return ReturnError("ERROR"); + }; + int error_line = __builtin_LINE() - 3; + int func_line = error_line; + + EXPECT_THAT(func().message(), Eq("EXPECTED")); + CheckSourceLocation(func(), {error_line, func_line}); + } + { + auto func1 = []() -> absl::Status { + ABSL_RETURN_IF_ERROR(ReturnOk()); + ABSL_RETURN_IF_ERROR(ReturnOk()); + ABSL_RETURN_IF_ERROR(absl::Status(StatusCode::kUnknown, "EXPECTED")); + return ReturnError("ERROR"); + }; + int error_line = __builtin_LINE() - 3; + int func_line = error_line; + + auto func3 = [=]() -> absl::Status { + ABSL_RETURN_IF_ERROR(func1()); + return ReturnError("ERROR_Func3"); + }; + int func3_line = __builtin_LINE() - 3; + + auto func4 = [=]() -> absl::Status { + ABSL_RETURN_IF_ERROR(func3()); + return ReturnError("ERROR_Func4"); + }; + int func4_line = __builtin_LINE() - 3; + CheckSourceLocation(func4(), + {error_line, func_line, func3_line, func4_line}); + } + { + auto func1 = []() -> absl::Status { + ABSL_RETURN_IF_ERROR(ReturnOk()); + ABSL_RETURN_IF_ERROR(ReturnOk()); + ABSL_RETURN_IF_ERROR(absl::Status(StatusCode::kUnknown, "")); + return ReturnError("ERROR"); + }; + + auto func3 = [=]() -> absl::Status { + ABSL_RETURN_IF_ERROR(func1()); + return ReturnError("ERROR_Func3"); + }; + + auto func4 = [=]() -> absl::Status { + ABSL_RETURN_IF_ERROR(func3()); + return ReturnError("ERROR_Func4"); + }; + CheckSourceLocation(func4()); + } +} + +TEST(ReturnIfError, WorksWithSourceLocationOn2Arg) { + { + auto func = []() -> absl::Status { + ABSL_RETURN_IF_ERROR(ReturnOk()); + ABSL_RETURN_IF_ERROR(ReturnOk()); + ABSL_RETURN_IF_ERROR(absl::Status(absl::StatusCode::kUnknown, "EXPECTED")) + << "foo"; + return ReturnError("ERROR"); + }; + int error_line = __builtin_LINE() - 4; + int func_line = error_line; + + EXPECT_THAT(func().message(), Eq("EXPECTED; foo")); + CheckSourceLocation(func(), {error_line, func_line}); + } + { + auto func1 = []() -> absl::Status { + ABSL_RETURN_IF_ERROR(ReturnOk()); + ABSL_RETURN_IF_ERROR(ReturnOk()); + ABSL_RETURN_IF_ERROR(absl::Status(absl::StatusCode::kUnknown, "EXPECTED")) + << "foo"; + return ReturnError("ERROR"); + }; + int error_line = __builtin_LINE() - 4; + int func_line = error_line; + + auto func3 = [=]() -> absl::Status { + ABSL_RETURN_IF_ERROR(func1()); + return ReturnError("ERROR_Func3"); + }; + int func3_line = __builtin_LINE() - 3; + + auto func4 = [=]() -> absl::Status { + ABSL_RETURN_IF_ERROR(func3()); + return ReturnError("ERROR_Func4"); + }; + int func4_line = __builtin_LINE() - 3; + CheckSourceLocation(func4(), + {error_line, func_line, func3_line, func4_line}); + } + { + auto func1 = []() -> absl::Status { + ABSL_RETURN_IF_ERROR(ReturnOk()); + ABSL_RETURN_IF_ERROR(ReturnOk()); + ABSL_RETURN_IF_ERROR(absl::Status(absl::StatusCode::kUnknown, "")); + return ReturnError("ERROR"); + }; + + auto func3 = [=]() -> absl::Status { + ABSL_RETURN_IF_ERROR(func1()) << "foo"; + return ReturnError("ERROR_Func3"); + }; + int func3_line = __builtin_LINE() - 3; + + auto func4 = [=]() -> absl::Status { + ABSL_RETURN_IF_ERROR(func3()); + return ReturnError("ERROR_Func4"); + }; + int func4_line = __builtin_LINE() - 3; + EXPECT_THAT(func4().message(), Eq("foo")); + CheckSourceLocation(func4(), {func3_line, func4_line}); + } + { + auto func1 = []() -> absl::Status { + ABSL_RETURN_IF_ERROR(ReturnOk()); + ABSL_RETURN_IF_ERROR(ReturnOk()); + ABSL_RETURN_IF_ERROR(absl::Status(absl::StatusCode::kUnknown, "")) << ""; + return ReturnError("ERROR"); + }; + + auto func3 = [=]() -> absl::Status { + ABSL_RETURN_IF_ERROR(func1()); + return ReturnError("ERROR_Func3"); + }; + + auto func4 = [=]() -> absl::Status { + ABSL_RETURN_IF_ERROR(func3()); + return ReturnError("ERROR_Func4"); + }; + CheckSourceLocation(func4()); + } +} + +TEST(ReturnIfError, WorksWithBuilder) { + auto func = []() -> absl::Status { + ABSL_RETURN_IF_ERROR(ReturnOkBuilder()); + ABSL_RETURN_IF_ERROR(ReturnOkBuilder()); + ABSL_RETURN_IF_ERROR(ReturnErrorBuilder("EXPECTED")); + return ReturnErrorBuilder("ERROR"); + }; + + EXPECT_THAT(func().message(), Eq("EXPECTED")); + + // Test that ABSL_RETURN_IF_ERROR can also return the StatusBuilder directly. + auto func2 = []() -> absl::StatusBuilder { + ABSL_RETURN_IF_ERROR(ReturnOkBuilder()); + ABSL_RETURN_IF_ERROR(ReturnOkBuilder()); + ABSL_RETURN_IF_ERROR(ReturnErrorBuilder("EXPECTED")); + return ReturnErrorBuilder("ERROR"); + }; + EXPECT_THAT(static_cast<absl::Status>(func2()).message(), Eq("EXPECTED")); +} + +TEST(ReturnIfError, IfInputIsBuilderDoesNotEagerlyConvertToStatus) { + auto func = []() -> absl::Status { + auto builder = ReturnErrorBuilder("FIRST"); + builder.SetPrepend(); + // If this call decays the builder to Status first and then makes another + // builder it will forget about the SetPrepend and the streaming will happen + // in the wrong order. + ABSL_RETURN_IF_ERROR(builder) << "SECOND "; + return absl::OkStatus(); + }; + EXPECT_THAT(func().message(), Eq("SECOND FIRST")); +} + +TEST(ReturnIfError, WorksWithLambda) { + auto func = []() -> absl::Status { + ABSL_RETURN_IF_ERROR([] { return ReturnOk(); }()); + ABSL_RETURN_IF_ERROR([] { return ReturnError("EXPECTED"); }()); + return ReturnError("ERROR"); + }; + + EXPECT_THAT(func().message(), Eq("EXPECTED")); +} + +TEST(ReturnIfError, WorksWithAppend) { + auto fail_test_if_called = []() -> std::string { + ADD_FAILURE(); + return "FAILURE"; + }; + auto func = [&]() -> absl::Status { + ABSL_RETURN_IF_ERROR(ReturnOk()) << fail_test_if_called(); + ABSL_RETURN_IF_ERROR(ReturnError("EXPECTED A")) << "EXPECTED B"; + return absl::OkStatus(); + }; + + EXPECT_THAT(func().message(), + AllOf(HasSubstr("EXPECTED A"), HasSubstr("EXPECTED B"))); +} + +TEST(ReturnIfError, WorksWithVoidReturnAdaptor) { + int code = 0; + int phase = 0; + auto adaptor = [&](absl::Status status) -> void { code = phase; }; + auto func = [&]() -> void { + phase = 1; + ABSL_RETURN_IF_ERROR(ReturnOk()).With(adaptor); + phase = 2; + ABSL_RETURN_IF_ERROR(ReturnError("EXPECTED A")).With(adaptor); + phase = 3; + }; + + func(); + EXPECT_EQ(phase, 2); + EXPECT_EQ(code, 2); +} + +} // namespace
diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel index 25af3f2..805cfc2 100644 --- a/absl/strings/BUILD.bazel +++ b/absl/strings/BUILD.bazel
@@ -434,6 +434,7 @@ linkopts = ABSL_DEFAULT_LINKOPTS, visibility = [ "//absl:friends", + "//absl/status:__pkg__", "//visibility:private", ], deps = [
diff --git a/absl/types/any.h b/absl/types/any.h index 9026b1b..accb025 100644 --- a/absl/types/any.h +++ b/absl/types/any.h
@@ -25,6 +25,7 @@ #define ABSL_TYPES_ANY_H_ #include <any> // IWYU pragma: export +#include <initializer_list> #include "absl/base/config.h" @@ -35,10 +36,30 @@ namespace absl { ABSL_NAMESPACE_BEGIN + using any ABSL_DEPRECATE_AND_INLINE() = std::any; -using std::any_cast; + +template <typename T, typename... Args> +[[deprecated]] constexpr decltype(std::any_cast<T>(std::declval<Args>()...)) +any_cast(Args&&... args) { + return std::any_cast<T>(std::forward<Args>(args)...); +} + using bad_any_cast ABSL_DEPRECATE_AND_INLINE() = std::bad_any_cast; -using std::make_any; + +template <typename T, typename... Args> +[[deprecated]] constexpr decltype(std::make_any<T>(std::declval<Args>()...)) +make_any(Args&&... args) { + return std::make_any<T>(std::forward<Args>(args)...); +} + +template <typename T, typename U, typename... Args> +[[deprecated]] constexpr decltype(std::make_any<T>( + std::declval<std::initializer_list<U>>(), std::declval<Args>()...)) +make_any(std::initializer_list<U> il, Args&&... args) { + return std::make_any<T>(il, std::forward<Args>(args)...); +} + ABSL_NAMESPACE_END } // namespace absl
diff --git a/absl/types/optional.h b/absl/types/optional.h index 96197ab..c70efb1 100644 --- a/absl/types/optional.h +++ b/absl/types/optional.h
@@ -23,6 +23,7 @@ #ifndef ABSL_TYPES_OPTIONAL_H_ #define ABSL_TYPES_OPTIONAL_H_ +#include <initializer_list> #include <optional> #include "absl/base/config.h" @@ -35,7 +36,26 @@ using bad_optional_access ABSL_REFACTOR_INLINE = std::bad_optional_access; -using std::make_optional; +template <typename T> +ABSL_REFACTOR_INLINE constexpr decltype(std::make_optional( + std::declval<T>())) make_optional(T&& value) { + return std::make_optional(std::forward<T>(value)); +} + +template <typename T, typename... Args> +constexpr decltype(std::make_optional<T>( + std::declval<Args>()...)) make_optional(Args&&... args) { + return std::make_optional<T>(std::forward<Args>(args)...); +} + +template <typename T, typename U, typename... Args> +constexpr decltype(std::make_optional<T>( + std::declval<std::initializer_list<U>>(), + std::declval<Args>()...)) make_optional(std::initializer_list<U> il, + Args&&... args) { + return std::make_optional<T>(il, std::forward<Args>(args)...); +} + using std::nullopt; using nullopt_t ABSL_REFACTOR_INLINE
diff --git a/absl/types/variant.h b/absl/types/variant.h index fd90c63..49f3e24 100644 --- a/absl/types/variant.h +++ b/absl/types/variant.h
@@ -37,9 +37,36 @@ using bad_variant_access ABSL_REFACTOR_INLINE = std::bad_variant_access; -using std::get; -using std::get_if; -using std::holds_alternative; +template <size_t I, typename... Args> +[[deprecated]] constexpr auto get(Args&&... args) + -> decltype(std::get<I>(std::forward<Args>(args)...)) { + return std::get<I>(std::forward<Args>(args)...); +} + +template <typename T, typename... Args> +[[deprecated]] constexpr decltype(std::get<T>(std::declval<Args>()...)) get( + Args&&... args) { + return std::get<T>(std::forward<Args>(args)...); +} + +template <size_t I, typename... Args> +[[deprecated]] constexpr decltype(std::get_if<I>(std::declval<Args>()...)) +get_if(Args&&... args) { + return std::get_if<I>(std::forward<Args>(args)...); +} + +template <typename T, typename... Args> +[[deprecated]] constexpr decltype(std::get_if<T>(std::declval<Args>()...)) +get_if(Args&&... args) { + return std::get_if<T>(std::forward<Args>(args)...); +} + +template <typename T, typename... Args> +[[deprecated]] constexpr decltype(std::holds_alternative<T>( + std::declval<Args>()...)) +holds_alternative(Args&&... args) { + return std::holds_alternative<T>(std::forward<Args>(args)...); +} using monostate ABSL_REFACTOR_INLINE = std::monostate; @@ -67,7 +94,11 @@ inline constexpr size_t variant_size_v ABSL_REFACTOR_INLINE = std::variant_size_v<T>; -using std::visit; +template <typename... Args> +[[deprecated]] constexpr decltype(std::visit(std::declval<Args>()...)) visit( + Args&&... args) { + return std::visit(std::forward<Args>(args)...); +} namespace variant_internal { // Helper visitor for converting a variant<Ts...>` into another type (mostly
diff --git a/absl/utility/utility.h b/absl/utility/utility.h index 4dbc01c..dd699d4 100644 --- a/absl/utility/utility.h +++ b/absl/utility/utility.h
@@ -18,8 +18,10 @@ #include <cstddef> #include <cstdlib> #include <tuple> +#include <type_traits> #include <utility> +#include "absl/base/attributes.h" #include "absl/base/config.h" #include "absl/base/macros.h" @@ -35,9 +37,34 @@ // abstractions for platforms that had not yet provided them. Those // platforms are no longer supported. New code should simply use the // the ones from std directly. -using std::apply; -using std::exchange; -using std::forward; +template <class F, class T> +ABSL_DEPRECATE_AND_INLINE() +constexpr decltype(auto) + apply(F&& f, T&& t) noexcept(noexcept(std::apply(std::declval<F>(), + std::declval<T>()))) { + return std::apply(std::forward<F>(f), std::forward<T>(t)); +} + +template <class T1, class T2 = T1> +ABSL_DEPRECATE_AND_INLINE() +constexpr T1 exchange(T1& obj, T2&& new_value) noexcept( + noexcept(std::exchange(std::declval<T1&>(), std::declval<T2>()))) { + return std::exchange(obj, std::forward<T2>(new_value)); +} + +template <class T> +[[deprecated("Use std::forward instead.")]] [[nodiscard]] constexpr T&& forward( + std::remove_reference_t<T>& arg ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept { + // NOLINTNEXTLINE: Avoid warnings about T not being the spelled type of arg. + return std::forward<T>(arg); +} + +template <class T> +[[deprecated("Use std::forward instead.")]] [[nodiscard]] constexpr T&& forward( + std::remove_reference_t<T>&& arg ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept { + // NOLINTNEXTLINE: Avoid warnings about T not being the spelled type of arg. + return std::forward<T>(arg); +} inline constexpr const std::in_place_t& in_place ABSL_DEPRECATE_AND_INLINE() = std::in_place; @@ -69,7 +96,13 @@ using index_sequence_for ABSL_DEPRECATE_AND_INLINE() = std::index_sequence_for<T...>; -using std::make_from_tuple; +template <class T, class Tuple> +ABSL_DEPRECATE_AND_INLINE() +[[nodiscard]] constexpr decltype(std::make_from_tuple<T>(std::declval<Tuple>())) + make_from_tuple(Tuple&& arg) noexcept( + noexcept(std::make_from_tuple<T>(std::declval<Tuple>()))) { + return std::make_from_tuple<T>(std::forward<Tuple>(arg)); +} template <size_t N> using make_index_sequence ABSL_DEPRECATE_AND_INLINE() = @@ -79,7 +112,19 @@ using make_integer_sequence ABSL_DEPRECATE_AND_INLINE() = std::make_integer_sequence<T, N>; -using std::move; +template <class It, class OutIt> +[[deprecated("Use std::move instead.")]] +constexpr OutIt move(It&& begin, It&& end, OutIt&& output) { + return std::move(std::forward<It>(begin), std::forward<It>(end), + std::forward<OutIt>(output)); +} + +template <class T> +[[deprecated("Use std::move instead.")]] +[[nodiscard]] constexpr std::remove_reference_t<T>&& +move(T&& arg ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept { + return std::move(arg); // NOLINT(bugprone-move-forwarding-reference) +} #if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L // Backfill for std::nontype_t. An instance of this class can be provided as a
diff --git a/ci/linux_arm_clang-latest_libcxx_bazel.sh b/ci/linux_arm_clang-latest_libcxx_bazel.sh index 4c04c77..24d5e48 100755 --- a/ci/linux_arm_clang-latest_libcxx_bazel.sh +++ b/ci/linux_arm_clang-latest_libcxx_bazel.sh
@@ -69,7 +69,7 @@ for exceptions_mode in ${EXCEPTIONS_MODE}; do echo "--------------------------------------------------------------------" time docker run \ - --env="USE_BAZEL_VERSION=9.0.0" \ + --env="USE_BAZEL_VERSION=9.1.0" \ --env="CC=/opt/llvm/bin/clang" \ --env="BAZEL_CXXOPTS=-std=${std}:-nostdinc++" \ --env="BAZEL_LINKOPTS=-L/opt/llvm/lib/aarch64-unknown-linux-gnu:-lc++:-lc++abi:-lm:-Wl,-rpath=/opt/llvm/lib/aarch64-unknown-linux-gnu" \
diff --git a/ci/linux_clang-latest_libcxx_asan_bazel.sh b/ci/linux_clang-latest_libcxx_asan_bazel.sh index 7f36980..b8d2b5f 100755 --- a/ci/linux_clang-latest_libcxx_asan_bazel.sh +++ b/ci/linux_clang-latest_libcxx_asan_bazel.sh
@@ -72,7 +72,7 @@ for exceptions_mode in ${EXCEPTIONS_MODE}; do echo "--------------------------------------------------------------------" time docker run \ - --env="USE_BAZEL_VERSION=9.0.0" \ + --env="USE_BAZEL_VERSION=9.1.0" \ --env="CC=/opt/llvm/bin/clang" \ --env="BAZEL_CXXOPTS=-std=${std}:-nostdinc++" \ --env="BAZEL_LINKOPTS=-L/opt/llvm/lib/x86_64-unknown-linux-gnu:-lc++:-lc++abi:-lm:-Wl,-rpath=/opt/llvm/lib/x86_64-unknown-linux-gnu" \
diff --git a/ci/linux_clang-latest_libcxx_bazel.sh b/ci/linux_clang-latest_libcxx_bazel.sh index 47a21ea..99e92b9 100755 --- a/ci/linux_clang-latest_libcxx_bazel.sh +++ b/ci/linux_clang-latest_libcxx_bazel.sh
@@ -69,7 +69,7 @@ for exceptions_mode in ${EXCEPTIONS_MODE}; do echo "--------------------------------------------------------------------" time docker run \ - --env="USE_BAZEL_VERSION=9.0.0" \ + --env="USE_BAZEL_VERSION=9.1.0" \ --env="CC=/opt/llvm/bin/clang" \ --env="BAZEL_CXXOPTS=-std=${std}:-nostdinc++" \ --env="BAZEL_LINKOPTS=-L/opt/llvm/lib/x86_64-unknown-linux-gnu:-lc++:-lc++abi:-lm:-Wl,-rpath=/opt/llvm/lib/x86_64-unknown-linux-gnu" \
diff --git a/ci/linux_clang-latest_libcxx_tsan_bazel.sh b/ci/linux_clang-latest_libcxx_tsan_bazel.sh index 235971b..56c3c41 100755 --- a/ci/linux_clang-latest_libcxx_tsan_bazel.sh +++ b/ci/linux_clang-latest_libcxx_tsan_bazel.sh
@@ -69,11 +69,11 @@ for exceptions_mode in ${EXCEPTIONS_MODE}; do echo "--------------------------------------------------------------------" time docker run \ - --env="USE_BAZEL_VERSION=9.0.0" \ + --env="USE_BAZEL_VERSION=9.1.0" \ --env="CC=/opt/llvm/bin/clang" \ --env="BAZEL_CXXOPTS=-std=${std}:-nostdinc++" \ - --env="BAZEL_LINKOPTS=-L/opt/llvm-tsan/lib/x86_64-unknown-linux-gnu:-lc++:-lc++abi:-lm:-Wl,-rpath=/opt/llvm-tsan/lib/x86_64-unknown-linux-gnu" \ - --env="CPLUS_INCLUDE_PATH=/opt/llvm-tsan/include/c++/v1:/opt/llvm-tsan/include/x86_64-unknown-linux-gnu/c++/v1/" \ + --env="BAZEL_LINKOPTS=-L/opt/llvm-tsan/lib:-lc++:-lc++abi:-lgcc_s:-lm:-Wl,-rpath=/opt/llvm-tsan/lib" \ + --env="CPLUS_INCLUDE_PATH=/opt/llvm-tsan/include/c++/v1" \ --mount type=bind,source="${ABSEIL_ROOT}",target=/abseil-cpp,readonly \ --workdir=/abseil-cpp \ --cap-add=SYS_PTRACE \
diff --git a/ci/linux_clang-latest_libstdcxx_bazel.sh b/ci/linux_clang-latest_libstdcxx_bazel.sh index 6b9f0b4..2fc8210 100755 --- a/ci/linux_clang-latest_libstdcxx_bazel.sh +++ b/ci/linux_clang-latest_libstdcxx_bazel.sh
@@ -70,7 +70,7 @@ for exceptions_mode in ${EXCEPTIONS_MODE}; do echo "--------------------------------------------------------------------" time docker run \ - --env="USE_BAZEL_VERSION=9.0.0" \ + --env="USE_BAZEL_VERSION=9.1.0" \ --env="CC=/opt/llvm/bin/clang" \ --env="BAZEL_CXXOPTS=-std=${std}" \ --mount type=bind,source="${ABSEIL_ROOT}",target=/abseil-cpp,readonly \
diff --git a/ci/linux_docker_containers.sh b/ci/linux_docker_containers.sh index 8cb17f7..7b6e0a8 100644 --- a/ci/linux_docker_containers.sh +++ b/ci/linux_docker_containers.sh
@@ -16,7 +16,7 @@ # Test scripts should source this file to get the identifiers. readonly LINUX_ALPINE_CONTAINER="gcr.io/google.com/absl-177019/alpine:20230612" -readonly LINUX_CLANG_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest:20260131" -readonly LINUX_ARM_CLANG_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_arm_hybrid-latest:20260131" -readonly LINUX_GCC_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest:20260131" -readonly LINUX_GCC_FLOOR_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest:20260131" +readonly LINUX_CLANG_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest@sha256:baa99e65a3cc837bab8e5246f1870d25c76d0adf2b1d598ee5a3ec77fd2e19bd" +readonly LINUX_ARM_CLANG_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_arm_hybrid-latest@sha256:04479ae67993e1a70dc1730bcddfe3233ac3af178ede0b33044dd9c31b889ac8" +readonly LINUX_GCC_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest@sha256:baa99e65a3cc837bab8e5246f1870d25c76d0adf2b1d598ee5a3ec77fd2e19bd" +readonly LINUX_GCC_FLOOR_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest@sha256:baa99e65a3cc837bab8e5246f1870d25c76d0adf2b1d598ee5a3ec77fd2e19bd"
diff --git a/ci/linux_gcc-floor_libstdcxx_bazel.sh b/ci/linux_gcc-floor_libstdcxx_bazel.sh index b1ca8ef..765b784 100755 --- a/ci/linux_gcc-floor_libstdcxx_bazel.sh +++ b/ci/linux_gcc-floor_libstdcxx_bazel.sh
@@ -41,7 +41,7 @@ # Print information about the environment. docker run "${DOCKER_CONTAINER}" /usr/bin/fastfetch -c ci -docker run "${DOCKER_CONTAINER}" /opt/gcc-9/bin/gcc -v +docker run "${DOCKER_CONTAINER}" /opt/gcc-10/bin/gcc -v docker run "${DOCKER_CONTAINER}" cat /root/cached_bazel_versions # USE_BAZEL_CACHE=1 only works on Kokoro. @@ -69,10 +69,10 @@ for exceptions_mode in ${EXCEPTIONS_MODE}; do echo "--------------------------------------------------------------------" time docker run \ - --env="USE_BAZEL_VERSION=9.0.0" \ - --env="CC=/opt/gcc-9/bin/gcc" \ + --env="USE_BAZEL_VERSION=9.1.0" \ + --env="CC=/opt/gcc-10/bin/gcc" \ --env="BAZEL_CXXOPTS=-std=${std}" \ - --env="BAZEL_LINKOPTS=-L/opt/gcc-9/lib64:-Wl,-rpath=/opt/gcc-9/lib64" \ + --env="BAZEL_LINKOPTS=-L/opt/gcc-10/lib64:-Wl,-rpath=/opt/gcc-10/lib64" \ --volume="${ABSEIL_ROOT}:/abseil-cpp:ro" \ --workdir=/abseil-cpp \ --cap-add=SYS_PTRACE \
diff --git a/ci/linux_gcc-latest_libstdcxx_bazel.sh b/ci/linux_gcc-latest_libstdcxx_bazel.sh index 86130f5..509aeb9 100755 --- a/ci/linux_gcc-latest_libstdcxx_bazel.sh +++ b/ci/linux_gcc-latest_libstdcxx_bazel.sh
@@ -69,7 +69,7 @@ for exceptions_mode in ${EXCEPTIONS_MODE}; do echo "--------------------------------------------------------------------" time docker run \ - --env="USE_BAZEL_VERSION=9.0.0" \ + --env="USE_BAZEL_VERSION=9.1.0" \ --env="CC=/usr/local/bin/gcc" \ --env="BAZEL_CXXOPTS=-std=${std}" \ --mount type=bind,source="${ABSEIL_ROOT}",target=/abseil-cpp-ro,readonly \
diff --git a/ci/macos_xcode_bazel.sh b/ci/macos_xcode_bazel.sh index 1f509bd..54d06eb 100755 --- a/ci/macos_xcode_bazel.sh +++ b/ci/macos_xcode_bazel.sh
@@ -27,7 +27,7 @@ fi # If we are running on Kokoro, check for a versioned Bazel binary. -KOKORO_GFILE_BAZEL_BIN="bazel-9.0.0-darwin-x86_64" +KOKORO_GFILE_BAZEL_BIN="bazel-9.1.0-darwin-x86_64" if [[ ${KOKORO_GFILE_DIR:-} ]] && [[ -f ${KOKORO_GFILE_DIR}/${KOKORO_GFILE_BAZEL_BIN} ]]; then BAZEL_BIN="${KOKORO_GFILE_DIR}/${KOKORO_GFILE_BAZEL_BIN}" chmod +x ${BAZEL_BIN}
diff --git a/ci/windows_clangcl_bazel.bat b/ci/windows_clangcl_bazel.bat index ed5bc5a..42be0f7 100755 --- a/ci/windows_clangcl_bazel.bat +++ b/ci/windows_clangcl_bazel.bat
@@ -47,7 +47,7 @@ :: /google/data/rw/teams/absl/kokoro/windows. :: :: TODO(absl-team): Remove -Wno-microsoft-cast -%KOKORO_GFILE_DIR%\bazel-9.0.0-windows-x86_64.exe ^ +%KOKORO_GFILE_DIR%\bazel-9.1.0-windows-x86_64.exe ^ test ... ^ --compilation_mode=%COMPILATION_MODE% ^ --compiler=clang-cl ^
diff --git a/ci/windows_msvc_bazel.bat b/ci/windows_msvc_bazel.bat index f07bbfd..031d08f 100755 --- a/ci/windows_msvc_bazel.bat +++ b/ci/windows_msvc_bazel.bat
@@ -42,7 +42,7 @@ :: To upgrade Bazel, first download a new binary from :: https://github.com/bazelbuild/bazel/releases and copy it to :: /google/data/rw/teams/absl/kokoro/windows. -"%KOKORO_GFILE_DIR%\bazel-9.0.0-windows-x86_64.exe" ^ +"%KOKORO_GFILE_DIR%\bazel-9.1.0-windows-x86_64.exe" ^ test ... ^ --compilation_mode=%COMPILATION_MODE% ^ --copt=/WX ^