diff options
Diffstat (limited to 'absl')
29 files changed, 1478 insertions, 106 deletions
diff --git a/absl/algorithm/BUILD.bazel b/absl/algorithm/BUILD.bazel index 255b986e..5cd54960 100644 --- a/absl/algorithm/BUILD.bazel +++ b/absl/algorithm/BUILD.bazel @@ -41,6 +41,18 @@ cc_test( ], ) +cc_binary( + name = "algorithm_benchmark", + testonly = 1, + srcs = ["equal_benchmark.cc"], + copts = ABSL_TEST_COPTS, + deps = [ + ":algorithm", + "//absl/base:core_headers", + "@com_github_google_benchmark//:benchmark", + ], +) + cc_library( name = "container", hdrs = [ diff --git a/absl/algorithm/equal_benchmark.cc b/absl/algorithm/equal_benchmark.cc new file mode 100644 index 00000000..fea6d137 --- /dev/null +++ b/absl/algorithm/equal_benchmark.cc @@ -0,0 +1,128 @@ +// Copyright 2017 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 +// +// http://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 <cstdint> +#include <cstring> + +#include "absl/algorithm/algorithm.h" +#include "benchmark/benchmark.h" + +namespace { + +// The range of sequence sizes to benchmark. +constexpr int kMinBenchmarkSize = 1024; +constexpr int kMaxBenchmarkSize = 8 * 1024 * 1024; + +// A user-defined type for use in equality benchmarks. Note that we expect +// std::memcmp to win for this type: libstdc++'s std::equal only defers to +// memcmp for integral types. This is because it is not straightforward to +// guarantee that std::memcmp would produce a result "as-if" compared by +// operator== for other types (example gotchas: NaN floats, structs with +// padding). +struct EightBits { + explicit EightBits(int /* unused */) : data(0) {} + bool operator==(const EightBits& rhs) const { return data == rhs.data; } + uint8_t data; +}; + +template <typename T> +void BM_absl_equal_benchmark(benchmark::State& state) { + std::vector<T> xs(state.range(0), T(0)); + std::vector<T> ys = xs; + while (state.KeepRunning()) { + const bool same = absl::equal(xs.begin(), xs.end(), ys.begin(), ys.end()); + benchmark::DoNotOptimize(same); + } +} + +template <typename T> +void BM_std_equal_benchmark(benchmark::State& state) { + std::vector<T> xs(state.range(0), T(0)); + std::vector<T> ys = xs; + while (state.KeepRunning()) { + const bool same = std::equal(xs.begin(), xs.end(), ys.begin()); + benchmark::DoNotOptimize(same); + } +} + +template <typename T> +void BM_memcmp_benchmark(benchmark::State& state) { + std::vector<T> xs(state.range(0), T(0)); + std::vector<T> ys = xs; + while (state.KeepRunning()) { + const bool same = + std::memcmp(xs.data(), ys.data(), xs.size() * sizeof(T)) == 0; + benchmark::DoNotOptimize(same); + } +} + +// The expectation is that the compiler should be able to elide the equality +// comparison altogether for sufficiently simple types. +template <typename T> +void BM_absl_equal_self_benchmark(benchmark::State& state) { + std::vector<T> xs(state.range(0), T(0)); + while (state.KeepRunning()) { + const bool same = absl::equal(xs.begin(), xs.end(), xs.begin(), xs.end()); + benchmark::DoNotOptimize(same); + } +} + +BENCHMARK_TEMPLATE(BM_absl_equal_benchmark, uint8_t) + ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); +BENCHMARK_TEMPLATE(BM_std_equal_benchmark, uint8_t) + ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); +BENCHMARK_TEMPLATE(BM_memcmp_benchmark, uint8_t) + ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); +BENCHMARK_TEMPLATE(BM_absl_equal_self_benchmark, uint8_t) + ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); + +BENCHMARK_TEMPLATE(BM_absl_equal_benchmark, uint16_t) + ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); +BENCHMARK_TEMPLATE(BM_std_equal_benchmark, uint16_t) + ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); +BENCHMARK_TEMPLATE(BM_memcmp_benchmark, uint16_t) + ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); +BENCHMARK_TEMPLATE(BM_absl_equal_self_benchmark, uint16_t) + ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); + +BENCHMARK_TEMPLATE(BM_absl_equal_benchmark, uint32_t) + ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); +BENCHMARK_TEMPLATE(BM_std_equal_benchmark, uint32_t) + ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); +BENCHMARK_TEMPLATE(BM_memcmp_benchmark, uint32_t) + ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); +BENCHMARK_TEMPLATE(BM_absl_equal_self_benchmark, uint32_t) + ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); + +BENCHMARK_TEMPLATE(BM_absl_equal_benchmark, uint64_t) + ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); +BENCHMARK_TEMPLATE(BM_std_equal_benchmark, uint64_t) + ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); +BENCHMARK_TEMPLATE(BM_memcmp_benchmark, uint64_t) + ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); +BENCHMARK_TEMPLATE(BM_absl_equal_self_benchmark, uint64_t) + ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); + +BENCHMARK_TEMPLATE(BM_absl_equal_benchmark, EightBits) + ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); +BENCHMARK_TEMPLATE(BM_std_equal_benchmark, EightBits) + ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); +BENCHMARK_TEMPLATE(BM_memcmp_benchmark, EightBits) + ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); +BENCHMARK_TEMPLATE(BM_absl_equal_self_benchmark, EightBits) + ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); + +} // namespace + +BENCHMARK_MAIN(); diff --git a/absl/base/BUILD.bazel b/absl/base/BUILD.bazel index f9aac5a5..bb7a5981 100644 --- a/absl/base/BUILD.bazel +++ b/absl/base/BUILD.bazel @@ -148,6 +148,18 @@ cc_library( ) cc_test( + name = "atomic_hook_test", + size = "small", + srcs = ["internal/atomic_hook_test.cc"], + copts = ABSL_TEST_COPTS, + deps = [ + ":base", + ":core_headers", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( name = "bit_cast_test", size = "small", srcs = [ @@ -393,3 +405,16 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_binary( + name = "thread_identity_benchmark", + testonly = 1, + srcs = ["internal/thread_identity_benchmark.cc"], + copts = ABSL_TEST_COPTS, + visibility = ["//visibility:private"], + deps = [ + ":base", + "//absl/synchronization", + "@com_github_google_benchmark//:benchmark", + ], +) diff --git a/absl/base/CMakeLists.txt b/absl/base/CMakeLists.txt index 329a3d05..45640562 100644 --- a/absl/base/CMakeLists.txt +++ b/absl/base/CMakeLists.txt @@ -99,14 +99,18 @@ absl_library( if(BUILD_TESTING) # exception-safety testing library - set(EXCEPTION_SAFETY_TESTING_SRC "internal/exception_safety_testing.cc") + set(EXCEPTION_SAFETY_TESTING_SRC + "internal/exception_safety_testing.h" + "internal/exception_safety_testing.cc" + ) set(EXCEPTION_SAFETY_TESTING_PUBLIC_LIBRARIES ${ABSL_TEST_COMMON_LIBRARIES} absl::base absl::memory absl::meta absl::strings - absl::types + absl::optional + gtest ) absl_library( @@ -116,6 +120,8 @@ absl_library( ${EXCEPTION_SAFETY_TESTING_SRC} PUBLIC_LIBRARIES ${EXCEPTION_SAFETY_TESTING_PUBLIC_LIBRARIES} + PRIVATE_COMPILE_FLAGS + ${ABSL_EXCEPTIONS_FLAG} ) endif() @@ -163,6 +169,20 @@ absl_library( # # call once test +set(ATOMIC_HOOK_TEST_SRC "internal/atomic_hook_test.cc") +set(ATOMIC_HOOK_TEST_PUBLIC_LIBRARIES absl::base) + +absl_test( + TARGET + atomic_hook_test + SOURCES + ${ATOMIC_HOOK_TEST_SRC} + PUBLIC_LIBRARIES + ${ATOMIC_HOOK_TEST_PUBLIC_LIBRARIES} +) + + +# call once test set(CALL_ONCE_TEST_SRC "call_once_test.cc") set(CALL_ONCE_TEST_PUBLIC_LIBRARIES absl::base absl::synchronization) @@ -344,7 +364,14 @@ absl_test( #test exceptions_safety_testing_test set(EXCEPTION_SAFETY_TESTING_TEST_SRC "exception_safety_testing_test.cc") -set(EXCEPTION_SAFETY_TESTING_TEST_PUBLIC_LIBRARIES absl::base absl::memory absl::meta absl::strings absl::optional) +set(EXCEPTION_SAFETY_TESTING_TEST_PUBLIC_LIBRARIES + absl::base + absl_base_internal_exception_safety_testing + absl::memory + absl::meta + absl::strings + absl::optional +) absl_test( TARGET diff --git a/absl/base/exception_safety_testing_test.cc b/absl/base/exception_safety_testing_test.cc index 9bd8b9db..4507b946 100644 --- a/absl/base/exception_safety_testing_test.cc +++ b/absl/base/exception_safety_testing_test.cc @@ -83,6 +83,27 @@ TEST(ThrowingValueTest, ThrowingAssignment) { TestOp([&]() { bomb = bomb1; }); TestOp([&]() { bomb = std::move(bomb1); }); + + // Test that when assignment throws, the assignment should fail (lhs != rhs) + // and strong guarantee fails (lhs != lhs_copy). + { + ThrowingValue<> lhs(39), rhs(42); + ThrowingValue<> lhs_copy(lhs); + SetCountdown(); + EXPECT_THROW(lhs = rhs, TestException); + UnsetCountdown(); + EXPECT_NE(lhs, rhs); + EXPECT_NE(lhs_copy, lhs); + } + { + ThrowingValue<> lhs(39), rhs(42); + ThrowingValue<> lhs_copy(lhs), rhs_copy(rhs); + SetCountdown(); + EXPECT_THROW(lhs = std::move(rhs), TestException); + UnsetCountdown(); + EXPECT_NE(lhs, rhs_copy); + EXPECT_NE(lhs_copy, lhs); + } } TEST(ThrowingValueTest, ThrowingComparisons) { diff --git a/absl/base/internal/atomic_hook.h b/absl/base/internal/atomic_hook.h index 47d40139..b458511b 100644 --- a/absl/base/internal/atomic_hook.h +++ b/absl/base/internal/atomic_hook.h @@ -21,6 +21,12 @@ #include <cstdint> #include <utility> +#ifdef _MSC_FULL_VER +#define ABSL_HAVE_WORKING_ATOMIC_POINTER 0 +#else +#define ABSL_HAVE_WORKING_ATOMIC_POINTER 1 +#endif + namespace absl { namespace base_internal { @@ -29,9 +35,15 @@ class AtomicHook; // AtomicHook is a helper class, templatized on a raw function pointer type, for // implementing Abseil customization hooks. It is a callable object that -// dispatches to the registered hook, or performs a no-op (and returns a default +// dispatches to the registered hook. +// +// A default constructed object performs a no-op (and returns a default // constructed object) if no hook has been registered. // +// Hooks can be pre-registered via constant initialization, for example, +// ABSL_CONST_INIT static AtomicHook<void(*)()> my_hook(DefaultAction); +// and then changed at runtime via a call to Store(). +// // Reads and writes guarantee memory_order_acquire/memory_order_release // semantics. template <typename ReturnType, typename... Args> @@ -39,7 +51,19 @@ class AtomicHook<ReturnType (*)(Args...)> { public: using FnPtr = ReturnType (*)(Args...); - constexpr AtomicHook() : hook_(kInitialValue) {} + // Constructs an object that by default performs a no-op (and + // returns a default constructed object) when no hook as been registered. + constexpr AtomicHook() : AtomicHook(DummyFunction) {} + + // Constructs an object that by default dispatches to/returns the + // pre-registered default_fn when no hook has been registered at runtime. +#if ABSL_HAVE_WORKING_ATOMIC_POINTER + explicit constexpr AtomicHook(FnPtr default_fn) + : hook_(default_fn), default_fn_(default_fn) {} +#else + explicit constexpr AtomicHook(FnPtr default_fn) + : hook_(kUninitialized), default_fn_(default_fn) {} +#endif // Stores the provided function pointer as the value for this hook. // @@ -86,16 +110,7 @@ class AtomicHook<ReturnType (*)(Args...)> { // // This causes an issue when building with LLVM under Windows. To avoid this, // we use a less-efficient, intptr_t-based implementation on Windows. - -#ifdef _MSC_FULL_VER -#define ABSL_HAVE_WORKING_ATOMIC_POINTER 0 -#else -#define ABSL_HAVE_WORKING_ATOMIC_POINTER 1 -#endif - #if ABSL_HAVE_WORKING_ATOMIC_POINTER - static constexpr FnPtr kInitialValue = &DummyFunction; - // Return the stored value, or DummyFunction if no value has been stored. FnPtr DoLoad() const { return hook_.load(std::memory_order_acquire); } @@ -103,10 +118,9 @@ class AtomicHook<ReturnType (*)(Args...)> { // stored to this object. bool DoStore(FnPtr fn) { assert(fn); - FnPtr expected = DummyFunction; - hook_.compare_exchange_strong(expected, fn, std::memory_order_acq_rel, - std::memory_order_acquire); - const bool store_succeeded = (expected == DummyFunction); + FnPtr expected = default_fn_; + const bool store_succeeded = hook_.compare_exchange_strong( + expected, fn, std::memory_order_acq_rel, std::memory_order_acquire); const bool same_value_already_stored = (expected == fn); return store_succeeded || same_value_already_stored; } @@ -114,15 +128,15 @@ class AtomicHook<ReturnType (*)(Args...)> { std::atomic<FnPtr> hook_; #else // !ABSL_HAVE_WORKING_ATOMIC_POINTER // Use a sentinel value unlikely to be the address of an actual function. - static constexpr intptr_t kInitialValue = 0; + static constexpr intptr_t kUninitialized = 0; static_assert(sizeof(intptr_t) >= sizeof(FnPtr), "intptr_t can't contain a function pointer"); FnPtr DoLoad() const { const intptr_t value = hook_.load(std::memory_order_acquire); - if (value == 0) { - return DummyFunction; + if (value == kUninitialized) { + return default_fn_; } return reinterpret_cast<FnPtr>(value); } @@ -130,16 +144,17 @@ class AtomicHook<ReturnType (*)(Args...)> { bool DoStore(FnPtr fn) { assert(fn); const auto value = reinterpret_cast<intptr_t>(fn); - intptr_t expected = 0; - hook_.compare_exchange_strong(expected, value, std::memory_order_acq_rel, - std::memory_order_acquire); - const bool store_succeeded = (expected == 0); + intptr_t expected = kUninitialized; + const bool store_succeeded = hook_.compare_exchange_strong( + expected, value, std::memory_order_acq_rel, std::memory_order_acquire); const bool same_value_already_stored = (expected == value); return store_succeeded || same_value_already_stored; } std::atomic<intptr_t> hook_; #endif + + const FnPtr default_fn_; }; #undef ABSL_HAVE_WORKING_ATOMIC_POINTER diff --git a/absl/base/internal/atomic_hook_test.cc b/absl/base/internal/atomic_hook_test.cc new file mode 100644 index 00000000..cf740757 --- /dev/null +++ b/absl/base/internal/atomic_hook_test.cc @@ -0,0 +1,70 @@ +// Copyright 2018 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 +// +// http://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/base/internal/atomic_hook.h" + +#include "gtest/gtest.h" +#include "absl/base/attributes.h" + +namespace { + +int value = 0; +void TestHook(int x) { value = x; } + +TEST(AtomicHookTest, NoDefaultFunction) { + ABSL_CONST_INIT static absl::base_internal::AtomicHook<void(*)(int)> hook; + value = 0; + + // Test the default DummyFunction. + EXPECT_TRUE(hook.Load() == nullptr); + EXPECT_EQ(value, 0); + hook(1); + EXPECT_EQ(value, 0); + + // Test a stored hook. + hook.Store(TestHook); + EXPECT_TRUE(hook.Load() == TestHook); + EXPECT_EQ(value, 0); + hook(1); + EXPECT_EQ(value, 1); + + // Calling Store() with the same hook should not crash. + hook.Store(TestHook); + EXPECT_TRUE(hook.Load() == TestHook); + EXPECT_EQ(value, 1); + hook(2); + EXPECT_EQ(value, 2); +} + +TEST(AtomicHookTest, WithDefaultFunction) { + // Set the default value to TestHook at compile-time. + ABSL_CONST_INIT static absl::base_internal::AtomicHook<void (*)(int)> hook( + TestHook); + value = 0; + + // Test the default value is TestHook. + EXPECT_TRUE(hook.Load() == TestHook); + EXPECT_EQ(value, 0); + hook(1); + EXPECT_EQ(value, 1); + + // Calling Store() with the same hook should not crash. + hook.Store(TestHook); + EXPECT_TRUE(hook.Load() == TestHook); + EXPECT_EQ(value, 1); + hook(2); + EXPECT_EQ(value, 2); +} + +} // namespace diff --git a/absl/base/internal/exception_safety_testing.h b/absl/base/internal/exception_safety_testing.h index 32450465..bec3ab30 100644 --- a/absl/base/internal/exception_safety_testing.h +++ b/absl/base/internal/exception_safety_testing.h @@ -164,7 +164,7 @@ class ConstructorTracker { template <typename Factory, typename Operation, typename Invariant> absl::optional<testing::AssertionResult> TestSingleInvariantAtCountdownImpl( - const Factory& factory, const Operation& operation, int count, + const Factory& factory, Operation operation, int count, const Invariant& invariant) { auto t_ptr = factory(); absl::optional<testing::AssertionResult> current_res; @@ -277,10 +277,12 @@ enum class TypeSpec { */ template <TypeSpec Spec = TypeSpec::kEverythingThrows> class ThrowingValue : private exceptions_internal::TrackedObject { - constexpr static bool IsSpecified(TypeSpec spec) { + static constexpr bool IsSpecified(TypeSpec spec) { return static_cast<bool>(Spec & spec); } + static constexpr int kBadValue = 938550620; + public: ThrowingValue() : TrackedObject(ABSL_PRETTY_FUNCTION) { exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); @@ -318,6 +320,7 @@ class ThrowingValue : private exceptions_internal::TrackedObject { ThrowingValue& operator=(const ThrowingValue& other) noexcept( IsSpecified(TypeSpec::kNoThrowCopy)) { + dummy_ = kBadValue; if (!IsSpecified(TypeSpec::kNoThrowCopy)) { exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); } @@ -327,6 +330,7 @@ class ThrowingValue : private exceptions_internal::TrackedObject { ThrowingValue& operator=(ThrowingValue&& other) noexcept( IsSpecified(TypeSpec::kNoThrowMove)) { + dummy_ = kBadValue; if (!IsSpecified(TypeSpec::kNoThrowMove)) { exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); } @@ -630,7 +634,7 @@ enum class AllocSpec { */ template <typename T, AllocSpec Spec = AllocSpec::kEverythingThrows> class ThrowingAllocator : private exceptions_internal::TrackedObject { - constexpr static bool IsSpecified(AllocSpec spec) { + static constexpr bool IsSpecified(AllocSpec spec) { return static_cast<bool>(Spec & spec); } @@ -1030,6 +1034,12 @@ MakeExceptionSafetyTester() { return {}; } +// Always return false, intended to be used as a checker with +// TestExceptionSafety() to check that no exception is thrown. +inline bool nothrow_guarantee(const void*) { + return ::testing::AssertionFailure() << "Violating NoThrowGuarantee"; +} + } // namespace testing #endif // ABSL_BASE_INTERNAL_EXCEPTION_SAFETY_TESTING_H_ diff --git a/absl/base/internal/thread_identity_benchmark.cc b/absl/base/internal/thread_identity_benchmark.cc new file mode 100644 index 00000000..3ae57317 --- /dev/null +++ b/absl/base/internal/thread_identity_benchmark.cc @@ -0,0 +1,40 @@ +// Copyright 2017 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 +// +// http://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/base/internal/thread_identity.h" +#include "absl/synchronization/internal/create_thread_identity.h" +#include "absl/synchronization/internal/per_thread_sem.h" +#include "benchmark/benchmark.h" + +namespace { + +void BM_SafeCurrentThreadIdentity(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize( + absl::synchronization_internal::GetOrCreateCurrentThreadIdentity()); + } +} +BENCHMARK(BM_SafeCurrentThreadIdentity); + +void BM_UnsafeCurrentThreadIdentity(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize( + absl::base_internal::CurrentThreadIdentityIfPresent()); + } +} +BENCHMARK(BM_UnsafeCurrentThreadIdentity); + +} // namespace + +BENCHMARK_MAIN(); diff --git a/absl/container/BUILD.bazel b/absl/container/BUILD.bazel index 8bdf6312..69cd5195 100644 --- a/absl/container/BUILD.bazel +++ b/absl/container/BUILD.bazel @@ -62,6 +62,17 @@ cc_test( ], ) +cc_binary( + name = "fixed_array_benchmark", + testonly = 1, + srcs = ["fixed_array_benchmark.cc"], + copts = ABSL_TEST_COPTS + ["$(STACK_FRAME_UNLIMITED)"], + deps = [ + ":fixed_array", + "@com_github_google_benchmark//:benchmark", + ], +) + cc_library( name = "inlined_vector", hdrs = ["inlined_vector.h"], @@ -106,6 +117,19 @@ cc_test( ], ) +cc_binary( + name = "inlined_vector_benchmark", + testonly = 1, + srcs = ["inlined_vector_benchmark.cc"], + copts = ABSL_TEST_COPTS, + deps = [ + ":inlined_vector", + "//absl/base", + "//absl/strings", + "@com_github_google_benchmark//:benchmark", + ], +) + cc_library( name = "test_instance_tracker", testonly = 1, diff --git a/absl/container/fixed_array_benchmark.cc b/absl/container/fixed_array_benchmark.cc new file mode 100644 index 00000000..2d39898d --- /dev/null +++ b/absl/container/fixed_array_benchmark.cc @@ -0,0 +1,68 @@ +// Copyright 2017 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 +// +// http://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/container/fixed_array.h" + +#include <stddef.h> +#include <string> + +#include "benchmark/benchmark.h" + +namespace { + +// For benchmarking -- simple class with constructor and destructor that +// set an int to a constant.. +class SimpleClass { + public: + SimpleClass() : i(3) { } + ~SimpleClass() { i = 0; } + private: + int i; +}; + +template <typename C, size_t stack_size> +void BM_FixedArray(benchmark::State& state) { + const int size = state.range(0); + for (auto _ : state) { + absl::FixedArray<C, stack_size> fa(size); + benchmark::DoNotOptimize(fa.data()); + } +} +BENCHMARK_TEMPLATE(BM_FixedArray, char, absl::kFixedArrayUseDefault) + ->Range(0, 1 << 16); +BENCHMARK_TEMPLATE(BM_FixedArray, char, 0)->Range(0, 1 << 16); +BENCHMARK_TEMPLATE(BM_FixedArray, char, 1)->Range(0, 1 << 16); +BENCHMARK_TEMPLATE(BM_FixedArray, char, 16)->Range(0, 1 << 16); +BENCHMARK_TEMPLATE(BM_FixedArray, char, 256)->Range(0, 1 << 16); +BENCHMARK_TEMPLATE(BM_FixedArray, char, 65536)->Range(0, 1 << 16); + +BENCHMARK_TEMPLATE(BM_FixedArray, SimpleClass, absl::kFixedArrayUseDefault) + ->Range(0, 1 << 16); +BENCHMARK_TEMPLATE(BM_FixedArray, SimpleClass, 0)->Range(0, 1 << 16); +BENCHMARK_TEMPLATE(BM_FixedArray, SimpleClass, 1)->Range(0, 1 << 16); +BENCHMARK_TEMPLATE(BM_FixedArray, SimpleClass, 16)->Range(0, 1 << 16); +BENCHMARK_TEMPLATE(BM_FixedArray, SimpleClass, 256)->Range(0, 1 << 16); +BENCHMARK_TEMPLATE(BM_FixedArray, SimpleClass, 65536)->Range(0, 1 << 16); + +BENCHMARK_TEMPLATE(BM_FixedArray, std::string, absl::kFixedArrayUseDefault) + ->Range(0, 1 << 16); +BENCHMARK_TEMPLATE(BM_FixedArray, std::string, 0)->Range(0, 1 << 16); +BENCHMARK_TEMPLATE(BM_FixedArray, std::string, 1)->Range(0, 1 << 16); +BENCHMARK_TEMPLATE(BM_FixedArray, std::string, 16)->Range(0, 1 << 16); +BENCHMARK_TEMPLATE(BM_FixedArray, std::string, 256)->Range(0, 1 << 16); +BENCHMARK_TEMPLATE(BM_FixedArray, std::string, 65536)->Range(0, 1 << 16); + +} // namespace + +BENCHMARK_MAIN(); diff --git a/absl/container/inlined_vector_benchmark.cc b/absl/container/inlined_vector_benchmark.cc new file mode 100644 index 00000000..a2035e35 --- /dev/null +++ b/absl/container/inlined_vector_benchmark.cc @@ -0,0 +1,376 @@ +// Copyright 2017 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 +// +// http://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/container/inlined_vector.h" + +#include <string> +#include <vector> + +#include "absl/base/internal/raw_logging.h" +#include "absl/strings/str_cat.h" +#include "benchmark/benchmark.h" + +namespace { + +using IntVec = absl::InlinedVector<int, 8>; + +void BM_InlinedVectorFill(benchmark::State& state) { + const int len = state.range(0); + for (auto _ : state) { + IntVec v; + for (int i = 0; i < len; i++) { + v.push_back(i); + } + } + state.SetItemsProcessed(static_cast<int64_t>(state.iterations()) * len); +} +BENCHMARK(BM_InlinedVectorFill)->Range(0, 1024); + +void BM_InlinedVectorFillRange(benchmark::State& state) { + const int len = state.range(0); + std::unique_ptr<int[]> ia(new int[len]); + for (int i = 0; i < len; i++) { + ia[i] = i; + } + for (auto _ : state) { + IntVec v(ia.get(), ia.get() + len); + benchmark::DoNotOptimize(v); + } + state.SetItemsProcessed(static_cast<int64_t>(state.iterations()) * len); +} +BENCHMARK(BM_InlinedVectorFillRange)->Range(0, 1024); + +void BM_StdVectorFill(benchmark::State& state) { + const int len = state.range(0); + for (auto _ : state) { + std::vector<int> v; + for (int i = 0; i < len; i++) { + v.push_back(i); + } + } + state.SetItemsProcessed(static_cast<int64_t>(state.iterations()) * len); +} +BENCHMARK(BM_StdVectorFill)->Range(0, 1024); + +bool StringRepresentedInline(std::string s) { + const char* chars = s.data(); + std::string s1 = std::move(s); + return s1.data() != chars; +} + +void BM_InlinedVectorFillString(benchmark::State& state) { + const int len = state.range(0); + std::string strings[4] = {"a quite long string", + "another long string", + "012345678901234567", + "to cause allocation"}; + for (auto _ : state) { + absl::InlinedVector<std::string, 8> v; + for (int i = 0; i < len; i++) { + v.push_back(strings[i & 3]); + } + } + state.SetItemsProcessed(static_cast<int64_t>(state.iterations()) * len); +} +BENCHMARK(BM_InlinedVectorFillString)->Range(0, 1024); + +void BM_StdVectorFillString(benchmark::State& state) { + const int len = state.range(0); + std::string strings[4] = {"a quite long string", + "another long string", + "012345678901234567", + "to cause allocation"}; + for (auto _ : state) { + std::vector<std::string> v; + for (int i = 0; i < len; i++) { + v.push_back(strings[i & 3]); + } + } + state.SetItemsProcessed(static_cast<int64_t>(state.iterations()) * len); + // The purpose of the benchmark is to verify that inlined vector is + // efficient when moving is more efficent than copying. To do so, we + // use strings that are larger than the small std::string optimization. + ABSL_RAW_CHECK(!StringRepresentedInline(strings[0]), + "benchmarked with strings that are too small"); +} +BENCHMARK(BM_StdVectorFillString)->Range(0, 1024); + +struct Buffer { // some arbitrary structure for benchmarking. + char* base; + int length; + int capacity; + void* user_data; +}; + +void BM_InlinedVectorTenAssignments(benchmark::State& state) { + const int len = state.range(0); + using BufferVec = absl::InlinedVector<Buffer, 2>; + + BufferVec src; + src.resize(len); + + BufferVec dst; + for (auto _ : state) { + for (int i = 0; i < 10; ++i) { + dst = src; + } + } +} +BENCHMARK(BM_InlinedVectorTenAssignments) + ->Arg(0)->Arg(1)->Arg(2)->Arg(3)->Arg(4)->Arg(20); + +void BM_CreateFromContainer(benchmark::State& state) { + for (auto _ : state) { + absl::InlinedVector<int, 4> x(absl::InlinedVector<int, 4>{1, 2, 3}); + benchmark::DoNotOptimize(x); + } +} +BENCHMARK(BM_CreateFromContainer); + +struct LargeCopyableOnly { + LargeCopyableOnly() : d(1024, 17) {} + LargeCopyableOnly(const LargeCopyableOnly& o) = default; + LargeCopyableOnly& operator=(const LargeCopyableOnly& o) = default; + + std::vector<int> d; +}; + +struct LargeCopyableSwappable { + LargeCopyableSwappable() : d(1024, 17) {} + LargeCopyableSwappable(const LargeCopyableSwappable& o) = default; + LargeCopyableSwappable(LargeCopyableSwappable&& o) = delete; + + LargeCopyableSwappable& operator=(LargeCopyableSwappable o) { + using std::swap; + swap(*this, o); + return *this; + } + LargeCopyableSwappable& operator=(LargeCopyableSwappable&& o) = delete; + + friend void swap(LargeCopyableSwappable& a, LargeCopyableSwappable& b) { + using std::swap; + swap(a.d, b.d); + } + + std::vector<int> d; +}; + +struct LargeCopyableMovable { + LargeCopyableMovable() : d(1024, 17) {} + // Use implicitly defined copy and move. + + std::vector<int> d; +}; + +struct LargeCopyableMovableSwappable { + LargeCopyableMovableSwappable() : d(1024, 17) {} + LargeCopyableMovableSwappable(const LargeCopyableMovableSwappable& o) = + default; + LargeCopyableMovableSwappable(LargeCopyableMovableSwappable&& o) = default; + + LargeCopyableMovableSwappable& operator=(LargeCopyableMovableSwappable o) { + using std::swap; + swap(*this, o); + return *this; + } + LargeCopyableMovableSwappable& operator=(LargeCopyableMovableSwappable&& o) = + default; + + friend void swap(LargeCopyableMovableSwappable& a, + LargeCopyableMovableSwappable& b) { + using std::swap; + swap(a.d, b.d); + } + + std::vector<int> d; +}; + +template <typename ElementType> +void BM_SwapElements(benchmark::State& state) { + const int len = state.range(0); + using Vec = absl::InlinedVector<ElementType, 32>; + Vec a(len); + Vec b; + for (auto _ : state) { + using std::swap; + swap(a, b); + } +} +BENCHMARK_TEMPLATE(BM_SwapElements, LargeCopyableOnly)->Range(0, 1024); +BENCHMARK_TEMPLATE(BM_SwapElements, LargeCopyableSwappable)->Range(0, 1024); +BENCHMARK_TEMPLATE(BM_SwapElements, LargeCopyableMovable)->Range(0, 1024); +BENCHMARK_TEMPLATE(BM_SwapElements, LargeCopyableMovableSwappable) + ->Range(0, 1024); + +// The following benchmark is meant to track the efficiency of the vector size +// as a function of stored type via the benchmark label. It is not meant to +// output useful sizeof operator performance. The loop is a dummy operation +// to fulfill the requirement of running the benchmark. +template <typename VecType> +void BM_Sizeof(benchmark::State& state) { + int size = 0; + for (auto _ : state) { + VecType vec; + size = sizeof(vec); + } + state.SetLabel(absl::StrCat("sz=", size)); +} +BENCHMARK_TEMPLATE(BM_Sizeof, absl::InlinedVector<char, 1>); +BENCHMARK_TEMPLATE(BM_Sizeof, absl::InlinedVector<char, 4>); +BENCHMARK_TEMPLATE(BM_Sizeof, absl::InlinedVector<char, 7>); +BENCHMARK_TEMPLATE(BM_Sizeof, absl::InlinedVector<char, 8>); + +BENCHMARK_TEMPLATE(BM_Sizeof, absl::InlinedVector<int, 1>); +BENCHMARK_TEMPLATE(BM_Sizeof, absl::InlinedVector<int, 4>); +BENCHMARK_TEMPLATE(BM_Sizeof, absl::InlinedVector<int, 7>); +BENCHMARK_TEMPLATE(BM_Sizeof, absl::InlinedVector<int, 8>); + +BENCHMARK_TEMPLATE(BM_Sizeof, absl::InlinedVector<void*, 1>); +BENCHMARK_TEMPLATE(BM_Sizeof, absl::InlinedVector<void*, 4>); +BENCHMARK_TEMPLATE(BM_Sizeof, absl::InlinedVector<void*, 7>); +BENCHMARK_TEMPLATE(BM_Sizeof, absl::InlinedVector<void*, 8>); + +BENCHMARK_TEMPLATE(BM_Sizeof, absl::InlinedVector<std::string, 1>); +BENCHMARK_TEMPLATE(BM_Sizeof, absl::InlinedVector<std::string, 4>); +BENCHMARK_TEMPLATE(BM_Sizeof, absl::InlinedVector<std::string, 7>); +BENCHMARK_TEMPLATE(BM_Sizeof, absl::InlinedVector<std::string, 8>); + +void BM_InlinedVectorIndexInlined(benchmark::State& state) { + absl::InlinedVector<int, 8> v = {1, 2, 3, 4, 5, 6, 7}; + for (auto _ : state) { + for (int i = 0; i < 1000; ++i) { + benchmark::DoNotOptimize(v); + benchmark::DoNotOptimize(v[4]); + } + } + state.SetItemsProcessed(1000 * static_cast<int64_t>(state.iterations())); +} +BENCHMARK(BM_InlinedVectorIndexInlined); + +void BM_InlinedVectorIndexExternal(benchmark::State& state) { + absl::InlinedVector<int, 8> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + for (auto _ : state) { + for (int i = 0; i < 1000; ++i) { + benchmark::DoNotOptimize(v); + benchmark::DoNotOptimize(v[4]); + } + } + state.SetItemsProcessed(1000 * static_cast<int64_t>(state.iterations())); +} +BENCHMARK(BM_InlinedVectorIndexExternal); + +void BM_StdVectorIndex(benchmark::State& state) { + std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + for (auto _ : state) { + for (int i = 0; i < 1000; ++i) { + benchmark::DoNotOptimize(v); + benchmark::DoNotOptimize(v[4]); + } + } + state.SetItemsProcessed(1000 * static_cast<int64_t>(state.iterations())); +} +BENCHMARK(BM_StdVectorIndex); + +#define UNROLL_2(x) \ + benchmark::DoNotOptimize(x); \ + benchmark::DoNotOptimize(x); + +#define UNROLL_4(x) UNROLL_2(x) UNROLL_2(x) +#define UNROLL_8(x) UNROLL_4(x) UNROLL_4(x) +#define UNROLL_16(x) UNROLL_8(x) UNROLL_8(x); + +void BM_InlinedVectorDataInlined(benchmark::State& state) { + absl::InlinedVector<int, 8> v = {1, 2, 3, 4, 5, 6, 7}; + for (auto _ : state) { + UNROLL_16(v.data()); + } + state.SetItemsProcessed(16 * static_cast<int64_t>(state.iterations())); +} +BENCHMARK(BM_InlinedVectorDataInlined); + +void BM_InlinedVectorDataExternal(benchmark::State& state) { + absl::InlinedVector<int, 8> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + for (auto _ : state) { + UNROLL_16(v.data()); + } + state.SetItemsProcessed(16 * static_cast<int64_t>(state.iterations())); +} +BENCHMARK(BM_InlinedVectorDataExternal); + +void BM_StdVectorData(benchmark::State& state) { + std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + for (auto _ : state) { + UNROLL_16(v.data()); + } + state.SetItemsProcessed(16 * static_cast<int64_t>(state.iterations())); +} +BENCHMARK(BM_StdVectorData); + +void BM_InlinedVectorSizeInlined(benchmark::State& state) { + absl::InlinedVector<int, 8> v = {1, 2, 3, 4, 5, 6, 7}; + for (auto _ : state) { + UNROLL_16(v.size()); + } + state.SetItemsProcessed(16 * static_cast<int64_t>(state.iterations())); +} +BENCHMARK(BM_InlinedVectorSizeInlined); + +void BM_InlinedVectorSizeExternal(benchmark::State& state) { + absl::InlinedVector<int, 8> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + for (auto _ : state) { + UNROLL_16(v.size()); + } + state.SetItemsProcessed(16 * static_cast<int64_t>(state.iterations())); +} +BENCHMARK(BM_InlinedVectorSizeExternal); + +void BM_StdVectorSize(benchmark::State& state) { + std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + for (auto _ : state) { + UNROLL_16(v.size()); + } + state.SetItemsProcessed(16 * static_cast<int64_t>(state.iterations())); +} +BENCHMARK(BM_StdVectorSize); + +void BM_InlinedVectorEmptyInlined(benchmark::State& state) { + absl::InlinedVector<int, 8> v = {1, 2, 3, 4, 5, 6, 7}; + for (auto _ : state) { + UNROLL_16(v.empty()); + } + state.SetItemsProcessed(16 * static_cast<int64_t>(state.iterations())); +} +BENCHMARK(BM_InlinedVectorEmptyInlined); + +void BM_InlinedVectorEmptyExternal(benchmark::State& state) { + absl::InlinedVector<int, 8> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + for (auto _ : state) { + UNROLL_16(v.empty()); + } + state.SetItemsProcessed(16 * static_cast<int64_t>(state.iterations())); +} +BENCHMARK(BM_InlinedVectorEmptyExternal); + +void BM_StdVectorEmpty(benchmark::State& state) { + std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + for (auto _ : state) { + UNROLL_16(v.empty()); + } + state.SetItemsProcessed(16 * static_cast<int64_t>(state.iterations())); +} +BENCHMARK(BM_StdVectorEmpty); + +} // namespace + +BENCHMARK_MAIN(); diff --git a/absl/debugging/CMakeLists.txt b/absl/debugging/CMakeLists.txt index 03a0a617..4af2ec8a 100644 --- a/absl/debugging/CMakeLists.txt +++ b/absl/debugging/CMakeLists.txt @@ -21,7 +21,8 @@ list(APPEND DEBUGGING_PUBLIC_HEADERS "symbolize.h" ) - +# TODO(cohenjon) The below is all kinds of wrong. Make this match what we do in +# Bazel list(APPEND DEBUGGING_INTERNAL_HEADERS "internal/address_is_readable.h" "internal/demangle.h" @@ -32,12 +33,16 @@ list(APPEND DEBUGGING_INTERNAL_HEADERS "internal/vdso_support.h" ) - -list(APPEND STACKTRACE_SRC - "stacktrace.cc" +list(APPEND DEBUGGING_INTERNAL_SRC "internal/address_is_readable.cc" "internal/elf_mem_image.cc" "internal/vdso_support.cc" +) + + +list(APPEND STACKTRACE_SRC + "stacktrace.cc" + ${DEBUGGING_INTERNAL_SRC} ${DEBUGGING_PUBLIC_HEADERS} ${DEBUGGING_INTERNAL_HEADERS} ) @@ -50,6 +55,7 @@ list(APPEND SYMBOLIZE_SRC "internal/demangle.cc" ${DEBUGGING_PUBLIC_HEADERS} ${DEBUGGING_INTERNAL_HEADERS} + ${DEBUGGING_INTERNAL_SRC} ) list(APPEND FAILURE_SIGNAL_HANDLER_SRC @@ -77,6 +83,9 @@ absl_library( absl_symbolize SOURCES ${SYMBOLIZE_SRC} + PUBLIC_LIBRARIES + absl::base + absl_malloc_internal EXPORT_NAME symbolize ) @@ -87,7 +96,7 @@ absl_library( SOURCES ${FAILURE_SIGNAL_HANDLER_SRC} PUBLIC_LIBRARIES - absl_base absl_synchronization + absl_base absl::examine_stack absl::stacktrace absl_synchronization EXPORT_NAME failure_signal_handler ) @@ -135,13 +144,9 @@ absl_header_library( ## TESTS # -list(APPEND DEBUGGING_INTERNAL_TEST_HEADERS - "internal/stack_consumption.h" -) - list(APPEND STACK_CONSUMPTION_SRC "internal/stack_consumption.cc" - ${DEBUGGING_INTERNAL_TEST_HEADERS} + "internal/stack_consumption.h" ) absl_library( @@ -155,10 +160,13 @@ absl_test( TARGET absl_stack_consumption_test SOURCES - ${STACK_CONSUMPTION_SRC} + "internal/stack_consumption_test.cc" + PUBLIC_LIBRARIES + absl_stack_consumption + absl::base ) -list(APPEND DEMANGLE_TEST_SRC "demangle_test.cc") +list(APPEND DEMANGLE_TEST_SRC "internal/demangle_test.cc") absl_test( TARGET @@ -177,7 +185,7 @@ absl_test( SOURCES ${SYMBOLIZE_TEST_SRC} PUBLIC_LIBRARIES - absl_symbolize absl_stack_consumption + absl::base absl::memory absl_symbolize absl_stack_consumption ) list(APPEND FAILURE_SIGNAL_HANDLER_TEST_SRC "failure_signal_handler_test.cc") @@ -188,7 +196,12 @@ absl_test( SOURCES ${FAILURE_SIGNAL_HANDLER_TEST_SRC} PUBLIC_LIBRARIES - absl_examine_stack absl_stacktrace absl_symbolize + absl_examine_stack + absl_failure_signal_handler + absl_stacktrace + absl_symbolize + absl::base + absl::strings ) # test leak_check_test diff --git a/absl/debugging/failure_signal_handler.cc b/absl/debugging/failure_signal_handler.cc index 597ad144..3de45f0b 100644 --- a/absl/debugging/failure_signal_handler.cc +++ b/absl/debugging/failure_signal_handler.cc @@ -32,6 +32,7 @@ #include <atomic> #include <cerrno> #include <csignal> +#include <cstdio> #include <cstring> #include <ctime> diff --git a/absl/meta/CMakeLists.txt b/absl/meta/CMakeLists.txt index d56fced8..adb0ceb7 100644 --- a/absl/meta/CMakeLists.txt +++ b/absl/meta/CMakeLists.txt @@ -32,6 +32,8 @@ list(APPEND TYPE_TRAITS_TEST_SRC absl_header_library( TARGET absl_meta + PUBLIC_LIBRARIES + absl::base EXPORT_NAME meta ) @@ -42,7 +44,8 @@ absl_test( SOURCES ${TYPE_TRAITS_TEST_SRC} PUBLIC_LIBRARIES - ${TYPE_TRAITS_TEST_PUBLIC_LIBRARIES} absl::meta + absl::base + absl::meta ) diff --git a/absl/strings/str_join_test.cc b/absl/strings/str_join_test.cc index 03b60f03..c941f9c8 100644 --- a/absl/strings/str_join_test.cc +++ b/absl/strings/str_join_test.cc @@ -24,7 +24,6 @@ #include <map> #include <memory> #include <ostream> -#include <set> #include <tuple> #include <type_traits> #include <vector> diff --git a/absl/time/internal/cctz/BUILD.bazel b/absl/time/internal/cctz/BUILD.bazel index fe17b3e3..468470b4 100644 --- a/absl/time/internal/cctz/BUILD.bazel +++ b/absl/time/internal/cctz/BUILD.bazel @@ -80,6 +80,7 @@ cc_test( name = "time_zone_format_test", size = "small", srcs = ["src/time_zone_format_test.cc"], + data = [":zoneinfo"], deps = [ ":civil_time", ":time_zone", @@ -91,6 +92,7 @@ cc_test( name = "time_zone_lookup_test", size = "small", srcs = ["src/time_zone_lookup_test.cc"], + data = [":zoneinfo"], deps = [ ":civil_time", ":time_zone", @@ -103,3 +105,8 @@ cc_test( ### examples ### binaries + +filegroup( + name = "zoneinfo", + srcs = glob(["testdata/zoneinfo/**"]), +) diff --git a/absl/time/internal/cctz/include/cctz/civil_time_detail.h b/absl/time/internal/cctz/include/cctz/civil_time_detail.h index 4c39c7d1..d52eddcd 100644 --- a/absl/time/internal/cctz/include/cctz/civil_time_detail.h +++ b/absl/time/internal/cctz/include/cctz/civil_time_detail.h @@ -20,8 +20,8 @@ #include <ostream> #include <type_traits> -// Disable constexpr support unless we are using clang in C++14 mode. -#if __clang__ && __cpp_constexpr >= 201304 +// Disable constexpr support unless we are in C++14 mode. +#if __cpp_constexpr >= 201304 || _MSC_VER >= 1910 #define CONSTEXPR_D constexpr // data #define CONSTEXPR_F constexpr // function #define CONSTEXPR_M constexpr // member diff --git a/absl/time/internal/cctz/src/civil_time_test.cc b/absl/time/internal/cctz/src/civil_time_test.cc index 6df0395b..f6648c8f 100644 --- a/absl/time/internal/cctz/src/civil_time_test.cc +++ b/absl/time/internal/cctz/src/civil_time_test.cc @@ -37,7 +37,7 @@ std::string Format(const T& t) { } // namespace -#if __clang__ && __cpp_constexpr >= 201304 +#if __cpp_constexpr >= 201304 || _MSC_VER >= 1910 // Construction constexpr tests TEST(CivilTime, Normal) { @@ -319,7 +319,7 @@ TEST(CivilTime, YearDay) { constexpr int yd = get_yearday(cd); static_assert(yd == 28, "YearDay"); } -#endif // __clang__ && __cpp_constexpr >= 201304 +#endif // __cpp_constexpr >= 201304 || _MSC_VER >= 1910 // The remaining tests do not use constexpr. diff --git a/absl/time/internal/cctz/src/time_zone_fixed.cc b/absl/time/internal/cctz/src/time_zone_fixed.cc index 8d3b1442..65eba356 100644 --- a/absl/time/internal/cctz/src/time_zone_fixed.cc +++ b/absl/time/internal/cctz/src/time_zone_fixed.cc @@ -27,7 +27,7 @@ namespace cctz { namespace { // The prefix used for the internal names of fixed-offset zones. -const char kFixedOffsetPrefix[] = "Fixed/"; +const char kFixedOffsetPrefix[] = "Fixed/UTC"; int Parse02d(const char* p) { static const char kDigits[] = "0123456789"; @@ -50,13 +50,11 @@ bool FixedOffsetFromName(const std::string& name, sys_seconds* offset) { const std::size_t prefix_len = sizeof(kFixedOffsetPrefix) - 1; const char* const ep = kFixedOffsetPrefix + prefix_len; - if (name.size() != prefix_len + 12) // "<prefix>UTC+99:99:99" + if (name.size() != prefix_len + 9) // <prefix>+99:99:99 return false; if (!std::equal(kFixedOffsetPrefix, ep, name.begin())) return false; const char* np = name.data() + prefix_len; - if (*np++ != 'U' || *np++ != 'T' || *np++ != 'C') - return false; if (np[0] != '+' && np[0] != '-') return false; if (np[3] != ':' || np[6] != ':') // see note below about large offsets @@ -97,8 +95,8 @@ std::string FixedOffsetToName(const sys_seconds& offset) { } int hours = minutes / 60; minutes %= 60; - char buf[sizeof(kFixedOffsetPrefix) + sizeof("UTC-24:00:00")]; - snprintf(buf, sizeof(buf), "%sUTC%c%02d:%02d:%02d", + char buf[sizeof(kFixedOffsetPrefix) + sizeof("-24:00:00")]; + snprintf(buf, sizeof(buf), "%s%c%02d:%02d:%02d", kFixedOffsetPrefix, sign, hours, minutes, seconds); return buf; } @@ -106,22 +104,14 @@ std::string FixedOffsetToName(const sys_seconds& offset) { std::string FixedOffsetToAbbr(const sys_seconds& offset) { std::string abbr = FixedOffsetToName(offset); const std::size_t prefix_len = sizeof(kFixedOffsetPrefix) - 1; - const char* const ep = kFixedOffsetPrefix + prefix_len; - if (abbr.size() >= prefix_len) { - if (std::equal(kFixedOffsetPrefix, ep, abbr.begin())) { - abbr.erase(0, prefix_len); - if (abbr.size() == 12) { // UTC+99:99:99 - abbr.erase(9, 1); // UTC+99:9999 - abbr.erase(6, 1); // UTC+999999 - if (abbr[8] == '0' && abbr[9] == '0') { // UTC+999900 - abbr.erase(8, 2); // UTC+9999 - if (abbr[6] == '0' && abbr[7] == '0') { // UTC+9900 - abbr.erase(6, 2); // UTC+99 - if (abbr[4] == '0') { // UTC+09 - abbr.erase(4, 1); // UTC+9 - } - } - } + if (abbr.size() == prefix_len + 9) { // <prefix>+99:99:99 + abbr.erase(0, prefix_len); // +99:99:99 + abbr.erase(6, 1); // +99:9999 + abbr.erase(3, 1); // +999999 + if (abbr[5] == '0' && abbr[6] == '0') { // +999900 + abbr.erase(5, 2); // +9999 + if (abbr[3] == '0' && abbr[4] == '0') { // +9900 + abbr.erase(3, 2); // +99 } } } diff --git a/absl/time/internal/cctz/src/time_zone_lookup.cc b/absl/time/internal/cctz/src/time_zone_lookup.cc index fbd86e16..d549d862 100644 --- a/absl/time/internal/cctz/src/time_zone_lookup.cc +++ b/absl/time/internal/cctz/src/time_zone_lookup.cc @@ -134,6 +134,9 @@ time_zone local_time_zone() { time_zone tz; load_time_zone(name, &tz); // Falls back to UTC. + // TODO: Follow the RFC3339 "Unknown Local Offset Convention" and + // arrange for %z to generate "-0000" when we don't know the local + // offset because the load_time_zone() failed and we're using UTC. return tz; } diff --git a/absl/time/internal/cctz/src/time_zone_lookup_test.cc b/absl/time/internal/cctz/src/time_zone_lookup_test.cc index a5d73d54..2dfe53b2 100644 --- a/absl/time/internal/cctz/src/time_zone_lookup_test.cc +++ b/absl/time/internal/cctz/src/time_zone_lookup_test.cc @@ -1119,18 +1119,6 @@ TEST(TimeZoneEdgeCase, AfricaMonrovia) { auto tp = convert(civil_second(1972, 1, 6, 23, 59, 59), tz); ExpectTime(tp, tz, 1972, 1, 6, 23, 59, 59, -44.5 * 60, false, "MMT"); tp += seconds(1); -#ifndef TZDATA_2017B_IS_UBIQUITOUS - // The 2017b tzdata release moved the shift from -004430 to +00 - // from 1972-05-01 to 1972-01-07, so we temporarily accept both - // outcomes until 2017b is ubiquitous. - if (tz.lookup(tp).offset == -44.5 * 60) { - tp = convert(civil_second(1972, 4, 30, 23, 59, 59), tz); - ExpectTime(tp, tz, 1972, 4, 30, 23, 59, 59, -44.5 * 60, false, "LRT"); - tp += seconds(1); - ExpectTime(tp, tz, 1972, 5, 1, 0, 44, 30, 0 * 60, false, "GMT"); - return; - } -#endif ExpectTime(tp, tz, 1972, 1, 7, 0, 44, 30, 0 * 60, false, "GMT"); } diff --git a/absl/time/internal/test_util.cc b/absl/time/internal/test_util.cc index 419d859d..bbbef7da 100644 --- a/absl/time/internal/test_util.cc +++ b/absl/time/internal/test_util.cc @@ -26,8 +26,7 @@ namespace cctz = absl::time_internal::cctz; namespace absl { namespace time_internal { -// TODO(bww): Reinstate when the FixedTimeZone() abbreviations are updated. -#if 1 || GTEST_USES_SIMPLE_RE +#if GTEST_USES_SIMPLE_RE extern const char kZoneAbbrRE[] = ".*"; // just punt #else extern const char kZoneAbbrRE[] = "[A-Za-z]{3,4}|[-+][0-9]{2}([0-9]{2})?"; diff --git a/absl/types/BUILD.bazel b/absl/types/BUILD.bazel index 0bdb2f78..f8d53c26 100644 --- a/absl/types/BUILD.bazel +++ b/absl/types/BUILD.bazel @@ -223,3 +223,18 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "variant_exception_safety_test", + size = "small", + srcs = [ + "variant_exception_safety_test.cc", + ], + copts = ABSL_TEST_COPTS + ABSL_EXCEPTIONS_FLAG, + deps = [ + ":variant", + "//absl/base:exception_safety_testing", + "//absl/memory", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/absl/types/CMakeLists.txt b/absl/types/CMakeLists.txt index f51d126f..fbd83740 100644 --- a/absl/types/CMakeLists.txt +++ b/absl/types/CMakeLists.txt @@ -29,6 +29,9 @@ absl_header_library( TARGET absl_any PUBLIC_LIBRARIES + absl::bad_any_cast + absl::base + absl::meta absl::utility PRIVATE_COMPILE_FLAGS ${ABSL_EXCEPTIONS_FLAG} @@ -59,7 +62,6 @@ absl_library( SOURCES ${BAD_ANY_CAST_SRC} PUBLIC_LIBRARIES - absl::base absl::any EXPORT_NAME bad_any_cast ) @@ -76,7 +78,11 @@ absl_library( SOURCES ${OPTIONAL_SRC} PUBLIC_LIBRARIES + absl::bad_optional_access absl::base + absl::memory + absl::meta + absl::utility EXPORT_NAME optional ) @@ -143,7 +149,11 @@ absl_test( # test any_exception_safety_test set(ANY_EXCEPTION_SAFETY_TEST_SRC "any_exception_safety_test.cc") -set(ANY_EXCEPTION_SAFETY_TEST_PUBLIC_LIBRARIES absl::any absl::base absl::base_internal_exception_safety_testing) +set(ANY_EXCEPTION_SAFETY_TEST_PUBLIC_LIBRARIES + absl::any + absl::base + absl_base_internal_exception_safety_testing +) absl_test( TARGET diff --git a/absl/types/optional.h b/absl/types/optional.h index 98b29e59..4dcf4796 100644 --- a/absl/types/optional.h +++ b/absl/types/optional.h @@ -958,7 +958,8 @@ constexpr auto operator==(const optional<T>& x, const optional<U>& y) -> decltype(optional_internal::convertible_to_bool(*x == *y)) { return static_cast<bool>(x) != static_cast<bool>(y) ? false - : static_cast<bool>(x) == false ? true : *x == *y; + : static_cast<bool>(x) == false ? true + : static_cast<bool>(*x == *y); } // Returns: If bool(x) != bool(y), true; otherwise, if bool(x) == false, false; @@ -968,31 +969,32 @@ constexpr auto operator!=(const optional<T>& x, const optional<U>& y) -> decltype(optional_internal::convertible_to_bool(*x != *y)) { return static_cast<bool>(x) != static_cast<bool>(y) ? true - : static_cast<bool>(x) == false ? false : *x != *y; + : static_cast<bool>(x) == false ? false + : static_cast<bool>(*x != *y); } // Returns: If !y, false; otherwise, if !x, true; otherwise *x < *y. template <typename T, typename U> constexpr auto operator<(const optional<T>& x, const optional<U>& y) -> decltype(optional_internal::convertible_to_bool(*x < *y)) { - return !y ? false : !x ? true : *x < *y; + return !y ? false : !x ? true : static_cast<bool>(*x < *y); } // Returns: If !x, false; otherwise, if !y, true; otherwise *x > *y. template <typename T, typename U> constexpr auto operator>(const optional<T>& x, const optional<U>& y) -> decltype(optional_internal::convertible_to_bool(*x > *y)) { - return !x ? false : !y ? true : *x > *y; + return !x ? false : !y ? true : static_cast<bool>(*x > *y); } // Returns: If !x, true; otherwise, if !y, false; otherwise *x <= *y. template <typename T, typename U> constexpr auto operator<=(const optional<T>& x, const optional<U>& y) -> decltype(optional_internal::convertible_to_bool(*x <= *y)) { - return !x ? true : !y ? false : *x <= *y; + return !x ? true : !y ? false : static_cast<bool>(*x <= *y); } // Returns: If !y, true; otherwise, if !x, false; otherwise *x >= *y. template <typename T, typename U> constexpr auto operator>=(const optional<T>& x, const optional<U>& y) -> decltype(optional_internal::convertible_to_bool(*x >= *y)) { - return !y ? true : !x ? false : *x >= *y; + return !y ? true : !x ? false : static_cast<bool>(*x >= *y); } // Comparison with nullopt [optional.nullops] @@ -1054,62 +1056,62 @@ constexpr bool operator>=(nullopt_t, const optional<T>& x) noexcept { template <typename T, typename U> constexpr auto operator==(const optional<T>& x, const U& v) -> decltype(optional_internal::convertible_to_bool(*x == v)) { - return static_cast<bool>(x) ? *x == v : false; + return static_cast<bool>(x) ? static_cast<bool>(*x == v) : false; } template <typename T, typename U> constexpr auto operator==(const U& v, const optional<T>& x) -> decltype(optional_internal::convertible_to_bool(v == *x)) { - return static_cast<bool>(x) ? v == *x : false; + return static_cast<bool>(x) ? static_cast<bool>(v == *x) : false; } template <typename T, typename U> constexpr auto operator!=(const optional<T>& x, const U& v) -> decltype(optional_internal::convertible_to_bool(*x != v)) { - return static_cast<bool>(x) ? *x != v : true; + return static_cast<bool>(x) ? static_cast<bool>(*x != v) : true; } template <typename T, typename U> constexpr auto operator!=(const U& v, const optional<T>& x) -> decltype(optional_internal::convertible_to_bool(v != *x)) { - return static_cast<bool>(x) ? v != *x : true; + return static_cast<bool>(x) ? static_cast<bool>(v != *x) : true; } template <typename T, typename U> constexpr auto operator<(const optional<T>& x, const U& v) -> decltype(optional_internal::convertible_to_bool(*x < v)) { - return static_cast<bool>(x) ? *x < v : true; + return static_cast<bool>(x) ? static_cast<bool>(*x < v) : true; } template <typename T, typename U> constexpr auto operator<(const U& v, const optional<T>& x) -> decltype(optional_internal::convertible_to_bool(v < *x)) { - return static_cast<bool>(x) ? v < *x : false; + return static_cast<bool>(x) ? static_cast<bool>(v < *x) : false; } template <typename T, typename U> constexpr auto operator<=(const optional<T>& x, const U& v) -> decltype(optional_internal::convertible_to_bool(*x <= v)) { - return static_cast<bool>(x) ? *x <= v : true; + return static_cast<bool>(x) ? static_cast<bool>(*x <= v) : true; } template <typename T, typename U> constexpr auto operator<=(const U& v, const optional<T>& x) -> decltype(optional_internal::convertible_to_bool(v <= *x)) { - return static_cast<bool>(x) ? v <= *x : false; + return static_cast<bool>(x) ? static_cast<bool>(v <= *x) : false; } template <typename T, typename U> constexpr auto operator>(const optional<T>& x, const U& v) -> decltype(optional_internal::convertible_to_bool(*x > v)) { - return static_cast<bool>(x) ? *x > v : false; + return static_cast<bool>(x) ? static_cast<bool>(*x > v) : false; } template <typename T, typename U> constexpr auto operator>(const U& v, const optional<T>& x) -> decltype(optional_internal::convertible_to_bool(v > *x)) { - return static_cast<bool>(x) ? v > *x : true; + return static_cast<bool>(x) ? static_cast<bool>(v > *x) : true; } template <typename T, typename U> constexpr auto operator>=(const optional<T>& x, const U& v) -> decltype(optional_internal::convertible_to_bool(*x >= v)) { - return static_cast<bool>(x) ? *x >= v : false; + return static_cast<bool>(x) ? static_cast<bool>(*x >= v) : false; } template <typename T, typename U> constexpr auto operator>=(const U& v, const optional<T>& x) -> decltype(optional_internal::convertible_to_bool(v >= *x)) { - return static_cast<bool>(x) ? v >= *x : true; + return static_cast<bool>(x) ? static_cast<bool>(v >= *x) : true; } } // namespace absl diff --git a/absl/types/variant.h b/absl/types/variant.h index 52a311e3..7ae65abe 100644 --- a/absl/types/variant.h +++ b/absl/types/variant.h @@ -350,7 +350,7 @@ constexpr const variant_alternative_t<I, variant<Types...>>&& get( // get_if() // // Returns a pointer to the value currently stored within a given variant, if -// present, using either a unique alternative type amonst the variant's set of +// present, using either a unique alternative type amongst the variant's set of // alternative types, or the variant's index value. If such a value does not // exist, returns `nullptr`. // diff --git a/absl/types/variant_exception_safety_test.cc b/absl/types/variant_exception_safety_test.cc new file mode 100644 index 00000000..377e4afa --- /dev/null +++ b/absl/types/variant_exception_safety_test.cc @@ -0,0 +1,519 @@ +// Copyright 2017 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 +// +// http://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/types/variant.h" + +#include <iostream> +#include <memory> +#include <utility> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/internal/exception_safety_testing.h" +#include "absl/memory/memory.h" + +namespace absl { +namespace { + +using ::testing::MakeExceptionSafetyTester; +using ::testing::nothrow_guarantee; +using ::testing::strong_guarantee; +using ::testing::TestThrowingCtor; + +using Thrower = testing::ThrowingValue<>; +using CopyNothrow = testing::ThrowingValue<testing::TypeSpec::kNoThrowCopy>; +using MoveNothrow = testing::ThrowingValue<testing::TypeSpec::kNoThrowMove>; +using ThrowingAlloc = testing::ThrowingAllocator<Thrower>; +using ThrowerVec = std::vector<Thrower, ThrowingAlloc>; +using ThrowingVariant = + absl::variant<Thrower, CopyNothrow, MoveNothrow, ThrowerVec>; + +struct ConversionException {}; + +template <class T> +struct ExceptionOnConversion { + operator T() const { // NOLINT + throw ConversionException(); + } +}; + +// Forces a variant into the valueless by exception state. +void ToValuelessByException(ThrowingVariant& v) { // NOLINT + try { + v.emplace<Thrower>(); + v.emplace<Thrower>(ExceptionOnConversion<Thrower>()); + } catch (ConversionException& /*e*/) { + // This space intentionally left blank. + } +} + +// Check that variant is still in a usable state after an exception is thrown. +testing::AssertionResult CheckInvariants(ThrowingVariant* v) { + using testing::AssertionFailure; + using testing::AssertionSuccess; + + // Try using the active alternative + if (absl::holds_alternative<Thrower>(*v)) { + auto& t = absl::get<Thrower>(*v); + t = Thrower{-100}; + if (t.Get() != -100) { + return AssertionFailure() << "Thrower should be assigned -100"; + } + } else if (absl::holds_alternative<ThrowerVec>(*v)) { + auto& tv = absl::get<ThrowerVec>(*v); + tv.clear(); + tv.emplace_back(-100); + if (tv.size() != 1 || tv[0].Get() != -100) { + return AssertionFailure() << "ThrowerVec should be {Thrower{-100}}"; + } + } else if (absl::holds_alternative<CopyNothrow>(*v)) { + auto& t = absl::get<CopyNothrow>(*v); + t = CopyNothrow{-100}; + if (t.Get() != -100) { + return AssertionFailure() << "CopyNothrow should be assigned -100"; + } + } else if (absl::holds_alternative<MoveNothrow>(*v)) { + auto& t = absl::get<MoveNothrow>(*v); + t = MoveNothrow{-100}; + if (t.Get() != -100) { + return AssertionFailure() << "MoveNothrow should be assigned -100"; + } + } + + // Try making variant valueless_by_exception + if (!v->valueless_by_exception()) ToValuelessByException(*v); + if (!v->valueless_by_exception()) { + return AssertionFailure() << "Variant should be valueless_by_exception"; + } + try { + auto unused = absl::get<Thrower>(*v); + static_cast<void>(unused); + return AssertionFailure() << "Variant should not contain Thrower"; + } catch (absl::bad_variant_access) { + } catch (...) { + return AssertionFailure() << "Unexpected exception throw from absl::get"; + } + + // Try using the variant + v->emplace<Thrower>(100); + if (!absl::holds_alternative<Thrower>(*v) || + absl::get<Thrower>(*v) != Thrower(100)) { + return AssertionFailure() << "Variant should contain Thrower(100)"; + } + v->emplace<ThrowerVec>({Thrower(100)}); + if (!absl::holds_alternative<ThrowerVec>(*v) || + absl::get<ThrowerVec>(*v)[0] != Thrower(100)) { + return AssertionFailure() + << "Variant should contain ThrowerVec{Thrower(100)}"; + } + return AssertionSuccess(); +} + +Thrower ExpectedThrower() { return Thrower(42); } +ThrowerVec ExpectedThrowerVec() { return {Thrower(100), Thrower(200)}; } +ThrowingVariant ValuelessByException() { + ThrowingVariant v; + ToValuelessByException(v); + return v; +} +ThrowingVariant WithThrower() { return Thrower(39); } +ThrowingVariant WithThrowerVec() { + return ThrowerVec{Thrower(1), Thrower(2), Thrower(3)}; +} +ThrowingVariant WithCopyNoThrow() { return CopyNothrow(39); } +ThrowingVariant WithMoveNoThrow() { return MoveNothrow(39); } + +TEST(VariantExceptionSafetyTest, DefaultConstructor) { + TestThrowingCtor<ThrowingVariant>(); +} + +TEST(VariantExceptionSafetyTest, CopyConstructor) { + { + ThrowingVariant v(ExpectedThrower()); + TestThrowingCtor<ThrowingVariant>(v); + } + { + ThrowingVariant v(ExpectedThrowerVec()); + TestThrowingCtor<ThrowingVariant>(v); + } + { + ThrowingVariant v(ValuelessByException()); + TestThrowingCtor<ThrowingVariant>(v); + } +} + +TEST(VariantExceptionSafetyTest, MoveConstructor) { + { + ThrowingVariant v(ExpectedThrower()); + TestThrowingCtor<ThrowingVariant>(std::move(v)); + } + { + ThrowingVariant v(ExpectedThrowerVec()); + TestThrowingCtor<ThrowingVariant>(std::move(v)); + } + { + ThrowingVariant v(ValuelessByException()); + TestThrowingCtor<ThrowingVariant>(std::move(v)); + } +} + +TEST(VariantExceptionSafetyTest, ValueConstructor) { + TestThrowingCtor<ThrowingVariant>(ExpectedThrower()); + TestThrowingCtor<ThrowingVariant>(ExpectedThrowerVec()); +} + +TEST(VariantExceptionSafetyTest, InPlaceTypeConstructor) { + TestThrowingCtor<ThrowingVariant>(absl::in_place_type_t<Thrower>{}, + ExpectedThrower()); + TestThrowingCtor<ThrowingVariant>(absl::in_place_type_t<ThrowerVec>{}, + ExpectedThrowerVec()); +} + +TEST(VariantExceptionSafetyTest, InPlaceIndexConstructor) { + TestThrowingCtor<ThrowingVariant>(absl::in_place_index_t<0>{}, + ExpectedThrower()); + TestThrowingCtor<ThrowingVariant>(absl::in_place_index_t<3>{}, + ExpectedThrowerVec()); +} + +TEST(VariantExceptionSafetyTest, CopyAssign) { + // variant& operator=(const variant& rhs); + // Let j be rhs.index() + { + // - neither *this nor rhs holds a value + const ThrowingVariant rhs = ValuelessByException(); + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(ValuelessByException()) + .WithInvariants(nothrow_guarantee) + .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; })); + } + { + // - *this holds a value but rhs does not + const ThrowingVariant rhs = ValuelessByException(); + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithInvariants(nothrow_guarantee) + .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; })); + } + // - index() == j + { + const ThrowingVariant rhs(ExpectedThrower()); + auto tester = + MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithOperation([&rhs](ThrowingVariant* lhs) { *lhs = rhs; }); + EXPECT_TRUE(tester.WithInvariants(CheckInvariants).Test()); + EXPECT_FALSE(tester.WithInvariants(strong_guarantee).Test()); + } + { + const ThrowingVariant rhs(ExpectedThrowerVec()); + auto tester = + MakeExceptionSafetyTester() + .WithInitialValue(WithThrowerVec()) + .WithOperation([&rhs](ThrowingVariant* lhs) { *lhs = rhs; }); + EXPECT_TRUE(tester.WithInvariants(CheckInvariants).Test()); + EXPECT_FALSE(tester.WithInvariants(strong_guarantee).Test()); + } + // libstdc++ std::variant has bugs on copy assignment regarding exception + // safety. +#if !(defined(ABSL_HAVE_STD_VARIANT) && defined(__GLIBCXX__)) + // index() != j + // if is_nothrow_copy_constructible_v<Tj> or + // !is_nothrow_move_constructible<Tj> is true, equivalent to + // emplace<j>(get<j>(rhs)) + { + // is_nothrow_copy_constructible_v<Tj> == true + // should not throw because emplace() invokes Tj's copy ctor + // which should not throw. + const ThrowingVariant rhs(CopyNothrow{}); + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithInvariants(nothrow_guarantee) + .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; })); + } + { + // is_nothrow_copy_constructible<Tj> == false && + // is_nothrow_move_constructible<Tj> == false + // should provide basic guarantee because emplace() invokes Tj's copy ctor + // which may throw. + const ThrowingVariant rhs(ExpectedThrower()); + auto tester = + MakeExceptionSafetyTester() + .WithInitialValue(WithCopyNoThrow()) + .WithOperation([&rhs](ThrowingVariant* lhs) { *lhs = rhs; }); + EXPECT_TRUE(tester + .WithInvariants(CheckInvariants, + [](ThrowingVariant* lhs) { + return lhs->valueless_by_exception(); + }) + .Test()); + EXPECT_FALSE(tester.WithInvariants(strong_guarantee).Test()); + } +#endif // !(defined(ABSL_HAVE_STD_VARIANT) && defined(__GLIBCXX__)) + { + // is_nothrow_copy_constructible_v<Tj> == false && + // is_nothrow_move_constructible_v<Tj> == true + // should provide strong guarantee because it is equivalent to + // operator=(variant(rhs)) which creates a temporary then invoke the move + // ctor which shouldn't throw. + const ThrowingVariant rhs(MoveNothrow{}); + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithInvariants(CheckInvariants, strong_guarantee) + .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; })); + } +} + +TEST(VariantExceptionSafetyTest, MoveAssign) { + // variant& operator=(variant&& rhs); + // Let j be rhs.index() + { + // - neither *this nor rhs holds a value + ThrowingVariant rhs = ValuelessByException(); + + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(ValuelessByException()) + .WithInvariants(nothrow_guarantee) + .Test([rhs](ThrowingVariant* lhs) mutable { + *lhs = std::move(rhs); + })); + } + { + // - *this holds a value but rhs does not + ThrowingVariant rhs = ValuelessByException(); + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithInvariants(nothrow_guarantee) + .Test([rhs](ThrowingVariant* lhs) mutable { + *lhs = std::move(rhs); + })); + } + { + // - index() == j + // assign get<j>(std::move(rhs)) to the value contained in *this. + // If an exception is thrown during call to Tj's move assignment, the state + // of the contained value is as defined by the exception safety guarantee of + // Tj's move assignment; index() will be j. + ThrowingVariant rhs(ExpectedThrower()); + size_t j = rhs.index(); + // Since Thrower's move assignment has basic guarantee, so should variant's. + auto tester = MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithOperation([rhs](ThrowingVariant* lhs) mutable { + *lhs = std::move(rhs); + }); + EXPECT_TRUE(tester + .WithInvariants( + CheckInvariants, + [j](ThrowingVariant* lhs) { return lhs->index() == j; }) + .Test()); + EXPECT_FALSE(tester.WithInvariants(strong_guarantee).Test()); + } + { + // - otherwise (index() != j), equivalent to + // emplace<j>(get<j>(std::move(rhs))) + // - If an exception is thrown during the call to Tj's move construction + // (with j being rhs.index()), the variant will hold no value. + ThrowingVariant rhs(CopyNothrow{}); + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithInvariants(CheckInvariants, + [](ThrowingVariant* lhs) { + return lhs->valueless_by_exception(); + }) + .Test([rhs](ThrowingVariant* lhs) mutable { + *lhs = std::move(rhs); + })); + } +} + +TEST(VariantExceptionSafetyTest, ValueAssign) { + // template<class T> variant& operator=(T&& t); + // Let Tj be the type that is selected by overload resolution to be assigned. + { + // If *this holds a Tj, assigns std::forward<T>(t) to the value contained in + // *this. If an exception is thrown during the assignment of + // std::forward<T>(t) to the value contained in *this, the state of the + // contained value and t are as defined by the exception safety guarantee of + // the assignment expression; valueless_by_exception() will be false. + // Since Thrower's copy/move assignment has basic guarantee, so should + // variant's. + Thrower rhs = ExpectedThrower(); + // copy assign + auto copy_tester = + MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithOperation([rhs](ThrowingVariant* lhs) { *lhs = rhs; }); + EXPECT_TRUE(copy_tester + .WithInvariants(CheckInvariants, + [](ThrowingVariant* lhs) { + return !lhs->valueless_by_exception(); + }) + .Test()); + EXPECT_FALSE(copy_tester.WithInvariants(strong_guarantee).Test()); + // move assign + auto move_tester = MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithOperation([rhs](ThrowingVariant* lhs) mutable { + *lhs = std::move(rhs); + }); + EXPECT_TRUE(move_tester + .WithInvariants(CheckInvariants, + [](ThrowingVariant* lhs) { + return !lhs->valueless_by_exception(); + }) + .Test()); + + EXPECT_FALSE(move_tester.WithInvariants(strong_guarantee).Test()); + } + // Otherwise (*this holds something else), if is_nothrow_constructible_v<Tj, + // T> || !is_nothrow_move_constructible_v<Tj> is true, equivalent to + // emplace<j>(std::forward<T>(t)). + // We simplify the test by letting T = `const Tj&` or `Tj&&`, so we can reuse + // the CopyNothrow and MoveNothrow types. + + // if is_nothrow_constructible_v<Tj, T> + // (i.e. is_nothrow_copy/move_constructible_v<Tj>) is true, emplace() just + // invokes the copy/move constructor and it should not throw. + { + const CopyNothrow rhs; + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithInvariants(nothrow_guarantee) + .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; })); + } + { + MoveNothrow rhs; + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithInvariants(nothrow_guarantee) + .Test([rhs](ThrowingVariant* lhs) mutable { + *lhs = std::move(rhs); + })); + } + // if is_nothrow_constructible_v<Tj, T> == false && + // is_nothrow_move_constructible<Tj> == false + // emplace() invokes the copy/move constructor which may throw so it should + // provide basic guarantee and variant object might not hold a value. + { + Thrower rhs = ExpectedThrower(); + // copy + auto copy_tester = + MakeExceptionSafetyTester() + .WithInitialValue(WithCopyNoThrow()) + .WithOperation([&rhs](ThrowingVariant* lhs) { *lhs = rhs; }); + EXPECT_TRUE(copy_tester + .WithInvariants(CheckInvariants, + [](ThrowingVariant* lhs) { + return lhs->valueless_by_exception(); + }) + .Test()); + EXPECT_FALSE(copy_tester.WithInvariants(strong_guarantee).Test()); + // move + auto move_tester = MakeExceptionSafetyTester() + .WithInitialValue(WithCopyNoThrow()) + .WithOperation([rhs](ThrowingVariant* lhs) mutable { + *lhs = std::move(rhs); + }); + EXPECT_TRUE(move_tester + .WithInvariants(CheckInvariants, + [](ThrowingVariant* lhs) { + return lhs->valueless_by_exception(); + }) + .Test()); + EXPECT_FALSE(move_tester.WithInvariants(strong_guarantee).Test()); + } + // Otherwise (if is_nothrow_constructible_v<Tj, T> == false && + // is_nothrow_move_constructible<Tj> == true), + // equivalent to operator=(variant(std::forward<T>(t))) + // This should have strong guarantee because it creates a temporary variant + // and operator=(variant&&) invokes Tj's move ctor which doesn't throw. + // libstdc++ std::variant has bugs on conversion assignment regarding + // exception safety. +#if !(defined(ABSL_HAVE_STD_VARIANT) && defined(__GLIBCXX__)) + { + MoveNothrow rhs; + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithInvariants(CheckInvariants, strong_guarantee) + .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; })); + } +#endif // !(defined(ABSL_HAVE_STD_VARIANT) && defined(__GLIBCXX__)) +} + +TEST(VariantExceptionSafetyTest, Emplace) { + // If an exception during the initialization of the contained value, the + // variant might not hold a value. The standard requires emplace() to provide + // only basic guarantee. + { + Thrower args = ExpectedThrower(); + auto tester = MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithOperation([&args](ThrowingVariant* v) { + v->emplace<Thrower>(args); + }); + EXPECT_TRUE(tester + .WithInvariants(CheckInvariants, + [](ThrowingVariant* v) { + return v->valueless_by_exception(); + }) + .Test()); + EXPECT_FALSE(tester.WithInvariants(strong_guarantee).Test()); + } +} + +TEST(VariantExceptionSafetyTest, Swap) { + // if both are valueless_by_exception(), no effect + { + ThrowingVariant rhs = ValuelessByException(); + EXPECT_TRUE( + MakeExceptionSafetyTester() + .WithInitialValue(ValuelessByException()) + .WithInvariants(nothrow_guarantee) + .Test([rhs](ThrowingVariant* lhs) mutable { lhs->swap(rhs); })); + } + // if index() == rhs.index(), calls swap(get<i>(*this), get<i>(rhs)) + // where i is index(). + { + ThrowingVariant rhs = ExpectedThrower(); + EXPECT_TRUE( + MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithInvariants(CheckInvariants) + .Test([rhs](ThrowingVariant* lhs) mutable { lhs->swap(rhs); })); + } + // Otherwise, exchanges the value of rhs and *this. The exception safety + // involves variant in moved-from state which is not specified in the + // standard, and since swap is 3-step it's impossible for it to provide a + // overall strong guarantee. So, we are only checking basic guarantee here. + { + ThrowingVariant rhs = ExpectedThrower(); + EXPECT_TRUE( + MakeExceptionSafetyTester() + .WithInitialValue(WithCopyNoThrow()) + .WithInvariants(CheckInvariants) + .Test([rhs](ThrowingVariant* lhs) mutable { lhs->swap(rhs); })); + } + { + ThrowingVariant rhs = ExpectedThrower(); + EXPECT_TRUE( + MakeExceptionSafetyTester() + .WithInitialValue(WithCopyNoThrow()) + .WithInvariants(CheckInvariants) + .Test([rhs](ThrowingVariant* lhs) mutable { rhs.swap(*lhs); })); + } +} + +} // namespace +} // namespace absl diff --git a/absl/utility/CMakeLists.txt b/absl/utility/CMakeLists.txt index df21b85b..dc3a6319 100644 --- a/absl/utility/CMakeLists.txt +++ b/absl/utility/CMakeLists.txt @@ -22,6 +22,8 @@ list(APPEND UTILITY_PUBLIC_HEADERS absl_header_library( TARGET absl_utility + PUBLIC_LIBRARIES + absl::base EXPORT_NAME utility ) @@ -33,7 +35,12 @@ absl_header_library( # test utility_test set(UTILITY_TEST_SRC "utility_test.cc") -set(UTILITY_TEST_PUBLIC_LIBRARIES absl::utility) +set(UTILITY_TEST_PUBLIC_LIBRARIES + absl::base + absl::memory + absl::strings + absl::utility +) absl_test( TARGET |