diff options
35 files changed, 1099 insertions, 576 deletions
diff --git a/absl/BUILD.bazel b/absl/BUILD.bazel index 439addbf..edd0274c 100644 --- a/absl/BUILD.bazel +++ b/absl/BUILD.bazel @@ -18,11 +18,10 @@ package(default_visibility = ["//visibility:public"]) licenses(["notice"]) # Apache 2.0 -config_setting( +load(":compiler_config_setting.bzl", "create_llvm_config") + +create_llvm_config( name = "llvm_compiler", - values = { - "compiler": "llvm", - }, visibility = [":__subpackages__"], ) diff --git a/absl/base/BUILD.bazel b/absl/base/BUILD.bazel index 35414a25..06d092eb 100644 --- a/absl/base/BUILD.bazel +++ b/absl/base/BUILD.bazel @@ -362,6 +362,7 @@ cc_test( copts = ABSL_TEST_COPTS, deps = [ ":base", + "//absl/strings", "@com_google_googletest//:gtest_main", ], ) diff --git a/absl/base/CMakeLists.txt b/absl/base/CMakeLists.txt index 303533e2..01d2af08 100644 --- a/absl/base/CMakeLists.txt +++ b/absl/base/CMakeLists.txt @@ -310,7 +310,7 @@ absl_test( # test raw_logging_test set(RAW_LOGGING_TEST_SRC "raw_logging_test.cc") -set(RAW_LOGGING_TEST_PUBLIC_LIBRARIES absl::base) +set(RAW_LOGGING_TEST_PUBLIC_LIBRARIES absl::base absl::strings) absl_test( TARGET diff --git a/absl/base/casts.h b/absl/base/casts.h index 8bd5264d..20fd34da 100644 --- a/absl/base/casts.h +++ b/absl/base/casts.h @@ -25,12 +25,36 @@ #define ABSL_BASE_CASTS_H_ #include <cstring> +#include <memory> #include <type_traits> #include "absl/base/internal/identity.h" +#include "absl/base/macros.h" namespace absl { +namespace internal_casts { + +// NOTE: Not a fully compliant implementation of `std::is_trivially_copyable`. +// TODO(calabrese) Branch on implementations that directly provide +// `std::is_trivially_copyable`, create a more rigorous workaround, and publicly +// expose in meta/type_traits. +template <class T> +struct is_trivially_copyable + : std::integral_constant< + bool, std::is_destructible<T>::value&& __has_trivial_destructor(T) && + __has_trivial_copy(T) && __has_trivial_assign(T)> {}; + +template <class Dest, class Source> +struct is_bitcastable + : std::integral_constant<bool, + sizeof(Dest) == sizeof(Source) && + is_trivially_copyable<Source>::value && + is_trivially_copyable<Dest>::value && + std::is_default_constructible<Dest>::value> {}; + +} // namespace internal_casts + // implicit_cast() // // Performs an implicit conversion between types following the language @@ -125,7 +149,32 @@ inline To implicit_cast(typename absl::internal::identity_t<To> to) { // and reading its bits back using a different type. A `bit_cast()` avoids this // issue by implementing its casts using `memcpy()`, which avoids introducing // this undefined behavior. -template <typename Dest, typename Source> +// +// NOTE: The requirements here are more strict than the bit_cast of standard +// proposal p0476 due to the need for workarounds and lack of intrinsics. +// Specifically, this implementation also requires `Dest` to be +// default-constructible. +template < + typename Dest, typename Source, + typename std::enable_if<internal_casts::is_bitcastable<Dest, Source>::value, + int>::type = 0> +inline Dest bit_cast(const Source& source) { + Dest dest; + memcpy(static_cast<void*>(std::addressof(dest)), + static_cast<const void*>(std::addressof(source)), sizeof(dest)); + return dest; +} + +// NOTE: This overload is only picked if the requirements of bit_cast are not +// met. It is therefore UB, but is provided temporarily as previous versions of +// this function template were unchecked. Do not use this in new code. +template < + typename Dest, typename Source, + typename std::enable_if< + !internal_casts::is_bitcastable<Dest, Source>::value, int>::type = 0> +ABSL_DEPRECATED( + "absl::bit_cast type requirements were violated. Update the types being " + "used such that they are the same size and are both TriviallyCopyable.") inline Dest bit_cast(const Source& source) { static_assert(sizeof(Dest) == sizeof(Source), "Source and destination types should have equal sizes."); diff --git a/absl/base/internal/direct_mmap.h b/absl/base/internal/direct_mmap.h index 2fe345fc..0426e118 100644 --- a/absl/base/internal/direct_mmap.h +++ b/absl/base/internal/direct_mmap.h @@ -92,11 +92,13 @@ inline void* DirectMmap(void* start, size_t length, int prot, int flags, int fd, #endif #elif defined(__s390x__) // On s390x, mmap() arguments are passed in memory. - uint32_t buf[6] = { - reinterpret_cast<uint32_t>(start), static_cast<uint32_t>(length), - static_cast<uint32_t>(prot), static_cast<uint32_t>(flags), - static_cast<uint32_t>(fd), static_cast<uint32_t>(offset)}; - return reintrepret_cast<void*>(syscall(SYS_mmap, buf)); + unsigned long buf[6] = {reinterpret_cast<unsigned long>(start), // NOLINT + static_cast<unsigned long>(length), // NOLINT + static_cast<unsigned long>(prot), // NOLINT + static_cast<unsigned long>(flags), // NOLINT + static_cast<unsigned long>(fd), // NOLINT + static_cast<unsigned long>(offset)}; // NOLINT + return reinterpret_cast<void*>(syscall(SYS_mmap, buf)); #elif defined(__x86_64__) // The x32 ABI has 32 bit longs, but the syscall interface is 64 bit. // We need to explicitly cast to an unsigned 64 bit type to avoid implicit diff --git a/absl/base/internal/raw_logging.cc b/absl/base/internal/raw_logging.cc index 1ce13888..41101bd7 100644 --- a/absl/base/internal/raw_logging.cc +++ b/absl/base/internal/raw_logging.cc @@ -206,6 +206,15 @@ void RawLog(absl::LogSeverity severity, const char* file, int line, va_end(ap); } +// Non-formatting version of RawLog(). +// +// TODO(gfalcon): When string_view no longer depends on base, change this +// interface to take its message as a string_view instead. +static void DefaultInternalLog(absl::LogSeverity severity, const char* file, + int line, const std::string& message) { + RawLog(severity, file, line, "%s", message.c_str()); +} + bool RawLoggingFullySupported() { #ifdef ABSL_LOW_LEVEL_WRITE_SUPPORTED return true; @@ -214,5 +223,12 @@ bool RawLoggingFullySupported() { #endif // !ABSL_LOW_LEVEL_WRITE_SUPPORTED } +ABSL_CONST_INIT absl::base_internal::AtomicHook<InternalLogFunction> + internal_log_function(DefaultInternalLog); + +void RegisterInternalLogFunction(InternalLogFunction func) { + internal_log_function.Store(func); +} + } // namespace raw_logging_internal } // namespace absl diff --git a/absl/base/internal/raw_logging.h b/absl/base/internal/raw_logging.h index a2b7207a..67abfd30 100644 --- a/absl/base/internal/raw_logging.h +++ b/absl/base/internal/raw_logging.h @@ -19,7 +19,10 @@ #ifndef ABSL_BASE_INTERNAL_RAW_LOGGING_H_ #define ABSL_BASE_INTERNAL_RAW_LOGGING_H_ +#include <string> + #include "absl/base/attributes.h" +#include "absl/base/internal/atomic_hook.h" #include "absl/base/log_severity.h" #include "absl/base/macros.h" #include "absl/base/port.h" @@ -57,6 +60,34 @@ } \ } while (0) +// ABSL_INTERNAL_LOG and ABSL_INTERNAL_CHECK work like the RAW variants above, +// except that if the richer log library is linked into the binary, we dispatch +// to that instead. This is potentially useful for internal logging and +// assertions, where we are using RAW_LOG neither for its async-signal-safety +// nor for its non-allocating nature, but rather because raw logging has very +// few other dependencies. +// +// The API is a subset of the above: each macro only takes two arguments. Use +// StrCat if you need to build a richer message. +#define ABSL_INTERNAL_LOG(severity, message) \ + do { \ + constexpr const char* absl_raw_logging_internal_basename = \ + ::absl::raw_logging_internal::Basename(__FILE__, \ + sizeof(__FILE__) - 1); \ + ::absl::raw_logging_internal::internal_log_function( \ + ABSL_RAW_LOGGING_INTERNAL_##severity, \ + absl_raw_logging_internal_basename, __LINE__, message); \ + } while (0) + +#define ABSL_INTERNAL_CHECK(condition, message) \ + do { \ + if (ABSL_PREDICT_FALSE(!(condition))) { \ + std::string death_message = "Check " #condition " failed: "; \ + death_message += std::string(message); \ + ABSL_INTERNAL_LOG(FATAL, death_message); \ + } \ + } while (0) + #define ABSL_RAW_LOGGING_INTERNAL_INFO ::absl::LogSeverity::kInfo #define ABSL_RAW_LOGGING_INTERNAL_WARNING ::absl::LogSeverity::kWarning #define ABSL_RAW_LOGGING_INTERNAL_ERROR ::absl::LogSeverity::kError @@ -131,6 +162,18 @@ using LogPrefixHook = bool (*)(absl::LogSeverity severity, const char* file, using AbortHook = void (*)(const char* file, int line, const char* buf_start, const char* prefix_end, const char* buf_end); +// Internal logging function for ABSL_INTERNAL_LOG to dispatch to. +// +// TODO(gfalcon): When string_view no longer depends on base, change this +// interface to take its message as a string_view instead. +using InternalLogFunction = void (*)(absl::LogSeverity severity, + const char* file, int line, + const std::string& message); + +extern base_internal::AtomicHook<InternalLogFunction> internal_log_function; + +void RegisterInternalLogFunction(InternalLogFunction func); + } // namespace raw_logging_internal } // namespace absl diff --git a/absl/base/raw_logging_test.cc b/absl/base/raw_logging_test.cc index dae4b351..ebbc5db9 100644 --- a/absl/base/raw_logging_test.cc +++ b/absl/base/raw_logging_test.cc @@ -18,12 +18,20 @@ #include "absl/base/internal/raw_logging.h" +#include <tuple> + #include "gtest/gtest.h" +#include "absl/strings/str_cat.h" namespace { TEST(RawLoggingCompilationTest, Log) { ABSL_RAW_LOG(INFO, "RAW INFO: %d", 1); + ABSL_RAW_LOG(INFO, "RAW INFO: %d %d", 1, 2); + ABSL_RAW_LOG(INFO, "RAW INFO: %d %d %d", 1, 2, 3); + ABSL_RAW_LOG(INFO, "RAW INFO: %d %d %d %d", 1, 2, 3, 4); + ABSL_RAW_LOG(INFO, "RAW INFO: %d %d %d %d %d", 1, 2, 3, 4, 5); + ABSL_RAW_LOG(WARNING, "RAW WARNING: %d", 1); ABSL_RAW_LOG(ERROR, "RAW ERROR: %d", 1); } @@ -47,4 +55,25 @@ TEST(RawLoggingDeathTest, LogFatal) { kExpectedDeathOutput); } +TEST(InternalLog, CompilationTest) { + ABSL_INTERNAL_LOG(INFO, "Internal Log"); + std::string log_msg = "Internal Log"; + ABSL_INTERNAL_LOG(INFO, log_msg); + + ABSL_INTERNAL_LOG(INFO, log_msg + " 2"); + + float d = 1.1f; + ABSL_INTERNAL_LOG(INFO, absl::StrCat("Internal log ", 3, " + ", d)); +} + +TEST(InternalLogDeathTest, FailingCheck) { + EXPECT_DEATH_IF_SUPPORTED(ABSL_INTERNAL_CHECK(1 == 0, "explanation"), + kExpectedDeathOutput); +} + +TEST(InternalLogDeathTest, LogFatal) { + EXPECT_DEATH_IF_SUPPORTED(ABSL_INTERNAL_LOG(FATAL, "my dog has fleas"), + kExpectedDeathOutput); +} + } // namespace diff --git a/absl/compiler_config_setting.bzl b/absl/compiler_config_setting.bzl new file mode 100644 index 00000000..b77c4f56 --- /dev/null +++ b/absl/compiler_config_setting.bzl @@ -0,0 +1,39 @@ +# +# 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. +# + +"""Creates config_setting that allows selecting based on 'compiler' value.""" + +def create_llvm_config(name, visibility): + # The "do_not_use_tools_cpp_compiler_present" attribute exists to + # distinguish between older versions of Bazel that do not support + # "@bazel_tools//tools/cpp:compiler" flag_value, and newer ones that do. + # In the future, the only way to select on the compiler will be through + # flag_values{"@bazel_tools//tools/cpp:compiler"} and the else branch can + # be removed. + if hasattr(cc_common, "do_not_use_tools_cpp_compiler_present"): + native.config_setting( + name = name, + flag_values = { + "@bazel_tools//tools/cpp:compiler": "llvm", + }, + visibility = visibility, + ) + else: + native.config_setting( + name = name, + values = {"compiler": "llvm"}, + visibility = visibility, + ) diff --git a/absl/container/fixed_array.h b/absl/container/fixed_array.h index 295f0108..62600df0 100644 --- a/absl/container/fixed_array.h +++ b/absl/container/fixed_array.h @@ -1,4 +1,4 @@ -// Copyright 2017 The Abseil Authors. +// 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. @@ -57,13 +57,13 @@ constexpr static auto kFixedArrayUseDefault = static_cast<size_t>(-1); // FixedArray // ----------------------------------------------------------------------------- // -// A `FixedArray` provides a run-time fixed-size array, allocating small arrays -// inline for efficiency and correctness. +// A `FixedArray` provides a run-time fixed-size array, allocating a small array +// inline for efficiency. // // Most users should not specify an `inline_elements` argument and let -// `FixedArray<>` automatically determine the number of elements +// `FixedArray` automatically determine the number of elements // to store inline based on `sizeof(T)`. If `inline_elements` is specified, the -// `FixedArray<>` implementation will inline arrays of +// `FixedArray` implementation will use inline storage for arrays with a // length <= `inline_elements`. // // Note that a `FixedArray` constructed with a `size_type` argument will @@ -78,19 +78,18 @@ constexpr static auto kFixedArrayUseDefault = static_cast<size_t>(-1); // operators. template <typename T, size_t inlined = kFixedArrayUseDefault> class FixedArray { + static_assert(!std::is_array<T>::value || std::extent<T>::value > 0, + "Arrays with unknown bounds cannot be used with FixedArray."); static constexpr size_t kInlineBytesDefault = 256; // std::iterator_traits isn't guaranteed to be SFINAE-friendly until C++17, // but this seems to be mostly pedantic. - template <typename Iter> - using EnableIfForwardIterator = typename std::enable_if< - std::is_convertible< - typename std::iterator_traits<Iter>::iterator_category, - std::forward_iterator_tag>::value, - int>::type; + template <typename Iterator> + using EnableIfForwardIterator = absl::enable_if_t<std::is_convertible< + typename std::iterator_traits<Iterator>::iterator_category, + std::forward_iterator_tag>::value>; public: - // For playing nicely with stl: using value_type = T; using iterator = T*; using const_iterator = const T*; @@ -112,40 +111,38 @@ class FixedArray { : FixedArray(other.begin(), other.end()) {} FixedArray(FixedArray&& other) noexcept( - // clang-format off - absl::allocator_is_nothrow<std::allocator<value_type>>::value && - // clang-format on - std::is_nothrow_move_constructible<value_type>::value) + absl::conjunction<absl::allocator_is_nothrow<std::allocator<value_type>>, + std::is_nothrow_move_constructible<value_type>>::value) : FixedArray(std::make_move_iterator(other.begin()), std::make_move_iterator(other.end())) {} // Creates an array object that can store `n` elements. // Note that trivially constructible elements will be uninitialized. - explicit FixedArray(size_type n) : rep_(n) { - absl::memory_internal::uninitialized_default_construct_n(rep_.begin(), + explicit FixedArray(size_type n) : storage_(n) { + absl::memory_internal::uninitialized_default_construct_n(storage_.begin(), size()); } // Creates an array initialized with `n` copies of `val`. - FixedArray(size_type n, const value_type& val) : rep_(n) { + FixedArray(size_type n, const value_type& val) : storage_(n) { std::uninitialized_fill_n(data(), size(), val); } // Creates an array initialized with the elements from the input // range. The array's size will always be `std::distance(first, last)`. - // REQUIRES: Iter must be a forward_iterator or better. - template <typename Iter, EnableIfForwardIterator<Iter> = 0> - FixedArray(Iter first, Iter last) : rep_(std::distance(first, last)) { + // REQUIRES: Iterator must be a forward_iterator or better. + template <typename Iterator, EnableIfForwardIterator<Iterator>* = nullptr> + FixedArray(Iterator first, Iterator last) + : storage_(std::distance(first, last)) { std::uninitialized_copy(first, last, data()); } - // Creates the array from an initializer_list. - FixedArray(std::initializer_list<T> init_list) + FixedArray(std::initializer_list<value_type> init_list) : FixedArray(init_list.begin(), init_list.end()) {} ~FixedArray() noexcept { - for (Holder* cur = rep_.begin(); cur != rep_.end(); ++cur) { - cur->~Holder(); + for (const StorageElement& cur : storage_) { + cur.~StorageElement(); } } @@ -157,7 +154,7 @@ class FixedArray { // FixedArray::size() // // Returns the length of the fixed array. - size_type size() const { return rep_.size(); } + size_type size() const { return storage_.size(); } // FixedArray::max_size() // @@ -182,12 +179,12 @@ class FixedArray { // // Returns a const T* pointer to elements of the `FixedArray`. This pointer // can be used to access (but not modify) the contained elements. - const_pointer data() const { return AsValue(rep_.begin()); } + const_pointer data() const { return AsValueType(storage_.begin()); } // Overload of FixedArray::data() to return a T* pointer to elements of the // fixed array. This pointer can be used to access and modify the contained // elements. - pointer data() { return AsValue(rep_.begin()); } + pointer data() { return AsValueType(storage_.begin()); } // FixedArray::operator[] // @@ -307,7 +304,7 @@ class FixedArray { // FixedArray::fill() // // Assigns the given `value` to all elements in the fixed array. - void fill(const T& value) { std::fill(begin(), end(), value); } + void fill(const value_type& val) { std::fill(begin(), end(), val); } // Relational operators. Equality operators are elementwise using // `operator==`, while order operators order FixedArrays lexicographically. @@ -337,17 +334,18 @@ class FixedArray { } private: - // HolderTraits + // StorageElement // - // Wrapper to hold elements of type T for the case where T is an array type. - // If 'T' is an array type, HolderTraits::type is a struct with a 'T v;'. - // Otherwise, HolderTraits::type is simply 'T'. + // For FixedArrays with a C-style-array value_type, StorageElement is a POD + // wrapper struct called StorageElementWrapper that holds the value_type + // instance inside. This is needed for construction and destruction of the + // entire array regardless of how many dimensions it has. For all other cases, + // StorageElement is just an alias of value_type. // - // Maintainer's Note: The simpler solution would be to simply wrap T in a - // struct whether it's an array or not: 'struct Holder { T v; };', but - // that causes some paranoid diagnostics to misfire about uses of data(), - // believing that 'data()' (aka '&rep_.begin().v') is a pointer to a single - // element, rather than the packed array that it really is. + // Maintainer's Note: The simpler solution would be to simply wrap value_type + // in a struct whether it's an array or not. That causes some paranoid + // diagnostics to misfire, believing that 'data()' returns a pointer to a + // single element, rather than the packed array that it really is. // e.g.: // // FixedArray<char> buf(1); @@ -356,130 +354,98 @@ class FixedArray { // error: call to int __builtin___sprintf_chk(etc...) // will always overflow destination buffer [-Werror] // - class HolderTraits { - template <typename U> - struct SelectImpl { - using type = U; - static pointer AsValue(type* p) { return p; } - }; - - // Partial specialization for elements of array type. - template <typename U, size_t N> - struct SelectImpl<U[N]> { - struct Holder { U v[N]; }; - using type = Holder; - static pointer AsValue(type* p) { return &p->v; } - }; - using Impl = SelectImpl<value_type>; - - public: - using type = typename Impl::type; - - static pointer AsValue(type *p) { return Impl::AsValue(p); } - - // TODO(billydonahue): fix the type aliasing violation - // this assertion hints at. - static_assert(sizeof(type) == sizeof(value_type), - "Holder must be same size as value_type"); + template <typename OuterT = value_type, + typename InnerT = absl::remove_extent_t<OuterT>, + size_t InnerN = std::extent<OuterT>::value> + struct StorageElementWrapper { + InnerT array[InnerN]; }; - using Holder = typename HolderTraits::type; - static pointer AsValue(Holder *p) { return HolderTraits::AsValue(p); } + using StorageElement = + absl::conditional_t<std::is_array<value_type>::value, + StorageElementWrapper<value_type>, value_type>; - // InlineSpace - // - // Allocate some space, not an array of elements of type T, so that we can - // skip calling the T constructors and destructors for space we never use. - // How many elements should we store inline? - // a. If not specified, use a default of kInlineBytesDefault bytes (This is - // currently 256 bytes, which seems small enough to not cause stack overflow - // or unnecessary stack pollution, while still allowing stack allocation for - // reasonably long character arrays). - // b. Never use 0 length arrays (not ISO C++) - // - template <size_type N, typename = void> - class InlineSpace { - public: - Holder* data() { return reinterpret_cast<Holder*>(space_.data()); } - void AnnotateConstruct(size_t n) const { Annotate(n, true); } - void AnnotateDestruct(size_t n) const { Annotate(n, false); } + static pointer AsValueType(pointer ptr) { return ptr; } + static pointer AsValueType(StorageElementWrapper<value_type>* ptr) { + return std::addressof(ptr->array); + } - private: -#ifndef ADDRESS_SANITIZER - void Annotate(size_t, bool) const { } -#else - void Annotate(size_t n, bool creating) const { - if (!n) return; - const void* bot = &left_redzone_; - const void* beg = space_.data(); - const void* end = space_.data() + n; - const void* top = &right_redzone_ + 1; - // args: (beg, end, old_mid, new_mid) - if (creating) { - ANNOTATE_CONTIGUOUS_CONTAINER(beg, top, top, end); - ANNOTATE_CONTIGUOUS_CONTAINER(bot, beg, beg, bot); - } else { - ANNOTATE_CONTIGUOUS_CONTAINER(beg, top, end, top); - ANNOTATE_CONTIGUOUS_CONTAINER(bot, beg, bot, beg); - } + static_assert(sizeof(StorageElement) == sizeof(value_type), ""); + static_assert(alignof(StorageElement) == alignof(value_type), ""); + + struct NonEmptyInlinedStorage { + using StorageElementBuffer = + absl::aligned_storage_t<sizeof(StorageElement), + alignof(StorageElement)>; + StorageElement* data() { + return reinterpret_cast<StorageElement*>(inlined_storage_.data()); } + +#ifdef ADDRESS_SANITIZER + void* RedzoneBegin() { return &redzone_begin_; } + void* RedzoneEnd() { return &redzone_end_ + 1; } #endif // ADDRESS_SANITIZER - using Buffer = - typename std::aligned_storage<sizeof(Holder), alignof(Holder)>::type; + void AnnotateConstruct(size_t); + void AnnotateDestruct(size_t); - ADDRESS_SANITIZER_REDZONE(left_redzone_); - std::array<Buffer, N> space_; - ADDRESS_SANITIZER_REDZONE(right_redzone_); + ADDRESS_SANITIZER_REDZONE(redzone_begin_); + std::array<StorageElementBuffer, inline_elements> inlined_storage_; + ADDRESS_SANITIZER_REDZONE(redzone_end_); }; - // specialization when N = 0. - template <typename U> - class InlineSpace<0, U> { - public: - Holder* data() { return nullptr; } - void AnnotateConstruct(size_t) const {} - void AnnotateDestruct(size_t) const {} + struct EmptyInlinedStorage { + StorageElement* data() { return nullptr; } + void AnnotateConstruct(size_t) {} + void AnnotateDestruct(size_t) {} }; - // Rep + using InlinedStorage = + absl::conditional_t<inline_elements == 0, EmptyInlinedStorage, + NonEmptyInlinedStorage>; + + // Storage // - // An instance of Rep manages the inline and out-of-line memory for FixedArray + // An instance of Storage manages the inline and out-of-line memory for + // instances of FixedArray. This guarantees that even when construction of + // individual elements fails in the FixedArray constructor body, the + // destructor for Storage will still be called and out-of-line memory will be + // properly deallocated. // - class Rep : public InlineSpace<inline_elements> { + class Storage : public InlinedStorage { public: - explicit Rep(size_type n) : n_(n), p_(MakeHolder(n)) {} - - ~Rep() noexcept { - if (IsAllocated(size())) { - std::allocator<Holder>().deallocate(p_, n_); - } else { + explicit Storage(size_type n) : data_(CreateStorage(n)), size_(n) {} + ~Storage() noexcept { + if (UsingInlinedStorage(size())) { this->AnnotateDestruct(size()); + } else { + std::allocator<StorageElement>().deallocate(begin(), size()); } } - Holder* begin() const { return p_; } - Holder* end() const { return p_ + n_; } - size_type size() const { return n_; } + + size_type size() const { return size_; } + StorageElement* begin() const { return data_; } + StorageElement* end() const { return begin() + size(); } private: - Holder* MakeHolder(size_type n) { - if (IsAllocated(n)) { - return std::allocator<Holder>().allocate(n); - } else { + static bool UsingInlinedStorage(size_type n) { + return n <= inline_elements; + } + + StorageElement* CreateStorage(size_type n) { + if (UsingInlinedStorage(n)) { this->AnnotateConstruct(n); - return this->data(); + return InlinedStorage::data(); + } else { + return std::allocator<StorageElement>().allocate(n); } } - bool IsAllocated(size_type n) const { return n > inline_elements; } - - const size_type n_; - Holder* const p_; + StorageElement* const data_; + const size_type size_; }; - - // Data members - Rep rep_; + const Storage storage_; }; template <typename T, size_t N> @@ -488,5 +454,25 @@ constexpr size_t FixedArray<T, N>::inline_elements; template <typename T, size_t N> constexpr size_t FixedArray<T, N>::kInlineBytesDefault; +template <typename T, size_t N> +void FixedArray<T, N>::NonEmptyInlinedStorage::AnnotateConstruct(size_t n) { +#ifdef ADDRESS_SANITIZER + if (!n) return; + ANNOTATE_CONTIGUOUS_CONTAINER(data(), RedzoneEnd(), RedzoneEnd(), data() + n); + ANNOTATE_CONTIGUOUS_CONTAINER(RedzoneBegin(), data(), data(), RedzoneBegin()); +#endif // ADDRESS_SANITIZER + static_cast<void>(n); // Mark used when not in asan mode +} + +template <typename T, size_t N> +void FixedArray<T, N>::NonEmptyInlinedStorage::AnnotateDestruct(size_t n) { +#ifdef ADDRESS_SANITIZER + if (!n) return; + ANNOTATE_CONTIGUOUS_CONTAINER(data(), RedzoneEnd(), data() + n, RedzoneEnd()); + ANNOTATE_CONTIGUOUS_CONTAINER(RedzoneBegin(), data(), RedzoneBegin(), data()); +#endif // ADDRESS_SANITIZER + static_cast<void>(n); // Mark used when not in asan mode +} + } // namespace absl #endif // ABSL_CONTAINER_FIXED_ARRAY_H_ diff --git a/absl/container/inlined_vector.h b/absl/container/inlined_vector.h index 03660f16..75d98027 100644 --- a/absl/container/inlined_vector.h +++ b/absl/container/inlined_vector.h @@ -689,11 +689,14 @@ class InlinedVector { new (&rep_.allocation_storage.allocation) Allocation(allocation); } + // TODO(absl-team): investigate whether the reinterpret_cast is appropriate. value_type* inlined_space() { - return reinterpret_cast<value_type*>(&rep_.inlined_storage.inlined); + return reinterpret_cast<value_type*>( + std::addressof(rep_.inlined_storage.inlined[0])); } const value_type* inlined_space() const { - return reinterpret_cast<const value_type*>(&rep_.inlined_storage.inlined); + return reinterpret_cast<const value_type*>( + std::addressof(rep_.inlined_storage.inlined[0])); } value_type* allocated_space() { return allocation().buffer(); } diff --git a/absl/copts.bzl b/absl/copts.bzl index 20c9b619..0168ac5a 100644 --- a/absl/copts.bzl +++ b/absl/copts.bzl @@ -31,7 +31,6 @@ GCC_TEST_FLAGS = [ "-Wno-unused-private-field", ] - # Docs on single flags is preceded by a comment. # Docs on groups of flags is preceded by ###. diff --git a/absl/debugging/internal/stacktrace_config.h b/absl/debugging/internal/stacktrace_config.h index 48adfccc..dd713da8 100644 --- a/absl/debugging/internal/stacktrace_config.h +++ b/absl/debugging/internal/stacktrace_config.h @@ -21,26 +21,16 @@ #ifndef ABSL_DEBUGGING_INTERNAL_STACKTRACE_CONFIG_H_ #define ABSL_DEBUGGING_INTERNAL_STACKTRACE_CONFIG_H_ -// First, test platforms which only support a stub. -#if ABSL_STACKTRACE_INL_HEADER +#if defined(ABSL_STACKTRACE_INL_HEADER) #error ABSL_STACKTRACE_INL_HEADER cannot be directly set -#elif defined(__native_client__) || defined(__APPLE__) || \ - defined(__FreeBSD__) || defined(__ANDROID__) || defined(__myriad2__) || \ - defined(__asmjs__) || defined(__wasm__) || defined(__Fuchsia__) -#define ABSL_STACKTRACE_INL_HEADER \ - "absl/debugging/internal/stacktrace_unimplemented-inl.inc" -// Next, test for Mips and Windows. -// TODO(marmstrong): Mips case, remove the check for ABSL_STACKTRACE_INL_HEADER -#elif defined(__mips__) && !defined(ABSL_STACKTRACE_INL_HEADER) -#define ABSL_STACKTRACE_INL_HEADER \ - "absl/debugging/internal/stacktrace_unimplemented-inl.inc" -#elif defined(_WIN32) // windows +#elif defined(_WIN32) #define ABSL_STACKTRACE_INL_HEADER \ "absl/debugging/internal/stacktrace_win32-inl.inc" -// Finally, test NO_FRAME_POINTER. -#elif !defined(NO_FRAME_POINTER) +#elif defined(__linux__) && !defined(__ANDROID__) + +#if !defined(NO_FRAME_POINTER) # if defined(__i386__) || defined(__x86_64__) #define ABSL_STACKTRACE_INL_HEADER \ "absl/debugging/internal/stacktrace_x86-inl.inc" @@ -53,22 +43,27 @@ # elif defined(__arm__) #define ABSL_STACKTRACE_INL_HEADER \ "absl/debugging/internal/stacktrace_arm-inl.inc" +# else +#define ABSL_STACKTRACE_INL_HEADER \ + "absl/debugging/internal/stacktrace_unimplemented-inl.inc" # endif #else // defined(NO_FRAME_POINTER) # if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) #define ABSL_STACKTRACE_INL_HEADER \ - "absl/debugging/internal/stacktrace_unimplemented-inl.inc" + "absl/debugging/internal/stacktrace_generic-inl.inc" # elif defined(__ppc__) || defined(__PPC__) -// Use glibc's backtrace. #define ABSL_STACKTRACE_INL_HEADER \ "absl/debugging/internal/stacktrace_generic-inl.inc" -# elif defined(__arm__) -# error stacktrace without frame pointer is not supported on ARM +# else +#define ABSL_STACKTRACE_INL_HEADER \ + "absl/debugging/internal/stacktrace_unimplemented-inl.inc" # endif #endif // NO_FRAME_POINTER -#if !defined(ABSL_STACKTRACE_INL_HEADER) -#error Not supported yet +#else +#define ABSL_STACKTRACE_INL_HEADER \ + "absl/debugging/internal/stacktrace_unimplemented-inl.inc" + #endif #endif // ABSL_DEBUGGING_INTERNAL_STACKTRACE_CONFIG_H_ diff --git a/absl/meta/type_traits.h b/absl/meta/type_traits.h index 88af17c3..c3e01fe2 100644 --- a/absl/meta/type_traits.h +++ b/absl/meta/type_traits.h @@ -369,4 +369,6 @@ struct IsHashEnabled } // namespace type_traits_internal } // namespace absl + + #endif // ABSL_META_TYPE_TRAITS_H_ diff --git a/absl/strings/str_format_test.cc b/absl/strings/str_format_test.cc index fe742bf9..fed75faf 100644 --- a/absl/strings/str_format_test.cc +++ b/absl/strings/str_format_test.cc @@ -384,8 +384,8 @@ TEST(StrFormat, BehavesAsDocumented) { EXPECT_EQ(StrFormat("%G", 1e10), "1E+10"); // a/A - lower,upper case hex Eg: -3.0 -> "-0x1.8p+1"/"-0X1.8P+1" -// On NDK r16, there is a regression in hexfloat formatting. -#if !defined(__NDK_MAJOR__) || __NDK_MAJOR__ != 16 +// On Android platform <=21, there is a regression in hexfloat formatting. +#if !defined(__ANDROID_API__) || __ANDROID_API__ > 21 EXPECT_EQ(StrFormat("%.1a", -3.0), "-0x1.8p+1"); // .1 to fix MSVC output EXPECT_EQ(StrFormat("%.1A", -3.0), "-0X1.8P+1"); // .1 to fix MSVC output #endif diff --git a/absl/synchronization/mutex.h b/absl/synchronization/mutex.h index 840b9d6b..4d9e0312 100644 --- a/absl/synchronization/mutex.h +++ b/absl/synchronization/mutex.h @@ -24,7 +24,7 @@ // Unlike a `std::mutex`, the Abseil `Mutex` provides the following additional // features: // * Conditional predicates intrinsic to the `Mutex` object -// * Reader/writer locks, in addition to standard exclusive/writer locks +// * Shared/reader locks, in addition to standard exclusive/writer locks // * Deadlock detection and debug support. // // The following helper classes are also defined within this file: @@ -290,7 +290,7 @@ class LOCKABLE Mutex { // Mutex::ReaderLockWhen() // Mutex::WriterLockWhen() // - // Blocks until simultaneously both `cond` is `true` and this` Mutex` can + // Blocks until simultaneously both `cond` is `true` and this `Mutex` can // be acquired, then atomically acquires this `Mutex`. `LockWhen()` is // logically equivalent to `*Lock(); Await();` though they may have different // performance characteristics. diff --git a/absl/synchronization/mutex_test.cc b/absl/synchronization/mutex_test.cc index 53b93784..b2820e20 100644 --- a/absl/synchronization/mutex_test.cc +++ b/absl/synchronization/mutex_test.cc @@ -29,6 +29,7 @@ #include <vector> #include "gtest/gtest.h" +#include "absl/base/attributes.h" #include "absl/base/internal/raw_logging.h" #include "absl/base/internal/sysinfo.h" #include "absl/memory/memory.h" @@ -54,8 +55,8 @@ CreateDefaultPool() { // Hack to schedule a function to run on a thread pool thread after a // duration has elapsed. static void ScheduleAfter(absl::synchronization_internal::ThreadPool *tp, - const std::function<void()> &func, - absl::Duration after) { + absl::Duration after, + const std::function<void()> &func) { tp->Schedule([func, after] { absl::SleepFor(after); func(); @@ -1150,249 +1151,369 @@ TEST(Mutex, DeadlockIdBug) NO_THREAD_SAFETY_ANALYSIS { // and so never expires/passes, and one that will expire/pass in the near // future. -// Encapsulate a Mutex-protected bool with its associated Condition/CondVar. -class Cond { - public: - explicit Cond(bool use_deadline) : use_deadline_(use_deadline), c_(&b_) {} - - void Set(bool v) { - absl::MutexLock lock(&mu_); - b_ = v; +static absl::Duration TimeoutTestAllowedSchedulingDelay() { + // Note: we use a function here because Microsoft Visual Studio fails to + // properly initialize constexpr static absl::Duration variables. + return absl::Milliseconds(150); +} + +// Returns true if `actual_delay` is close enough to `expected_delay` to pass +// the timeouts/deadlines test. Otherwise, logs warnings and returns false. +ABSL_MUST_USE_RESULT +static bool DelayIsWithinBounds(absl::Duration expected_delay, + absl::Duration actual_delay) { + bool pass = true; + // Do not allow the observed delay to be less than expected. This may occur + // in practice due to clock skew or when the synchronization primitives use a + // different clock than absl::Now(), but these cases should be handled by the + // the retry mechanism in each TimeoutTest. + if (actual_delay < expected_delay) { + ABSL_RAW_LOG(WARNING, + "Actual delay %s was too short, expected %s (difference %s)", + absl::FormatDuration(actual_delay).c_str(), + absl::FormatDuration(expected_delay).c_str(), + absl::FormatDuration(actual_delay - expected_delay).c_str()); + pass = false; } - - bool AwaitWithTimeout(absl::Duration timeout) { - absl::MutexLock lock(&mu_); - return use_deadline_ ? mu_.AwaitWithDeadline(c_, absl::Now() + timeout) - : mu_.AwaitWithTimeout(c_, timeout); + // If the expected delay is <= zero then allow a small error tolerance, since + // we do not expect context switches to occur during test execution. + // Otherwise, thread scheduling delays may be substantial in rare cases, so + // tolerate up to kTimeoutTestAllowedSchedulingDelay of error. + absl::Duration tolerance = expected_delay <= absl::ZeroDuration() + ? absl::Milliseconds(10) + : TimeoutTestAllowedSchedulingDelay(); + if (actual_delay > expected_delay + tolerance) { + ABSL_RAW_LOG(WARNING, + "Actual delay %s was too long, expected %s (difference %s)", + absl::FormatDuration(actual_delay).c_str(), + absl::FormatDuration(expected_delay).c_str(), + absl::FormatDuration(actual_delay - expected_delay).c_str()); + pass = false; } + return pass; +} + +// Parameters for TimeoutTest, below. +struct TimeoutTestParam { + // The file and line number (used for logging purposes only). + const char *from_file; + int from_line; + + // Should the absolute deadline API based on absl::Time be tested? If false, + // the relative deadline API based on absl::Duration is tested. + bool use_absolute_deadline; + + // The deadline/timeout used when calling the API being tested + // (e.g. Mutex::LockWhenWithDeadline). + absl::Duration wait_timeout; + + // The delay before the condition will be set true by the test code. If zero + // or negative, the condition is set true immediately (before calling the API + // being tested). Otherwise, if infinite, the condition is never set true. + // Otherwise a closure is scheduled for the future that sets the condition + // true. + absl::Duration satisfy_condition_delay; + + // The expected result of the condition after the call to the API being + // tested. Generally `true` means the condition was true when the API returns, + // `false` indicates an expected timeout. + bool expected_result; + + // The expected delay before the API under test returns. This is inherently + // flaky, so some slop is allowed (see `DelayIsWithinBounds` above), and the + // test keeps trying indefinitely until this constraint passes. + absl::Duration expected_delay; +}; - bool LockWhenWithTimeout(absl::Duration timeout) { - bool b = use_deadline_ ? mu_.LockWhenWithDeadline(c_, absl::Now() + timeout) - : mu_.LockWhenWithTimeout(c_, timeout); - mu_.Unlock(); - return b; +// Print a `TimeoutTestParam` to a debug log. +std::ostream &operator<<(std::ostream &os, const TimeoutTestParam ¶m) { + return os << "from: " << param.from_file << ":" << param.from_line + << " use_absolute_deadline: " + << (param.use_absolute_deadline ? "true" : "false") + << " wait_timeout: " << param.wait_timeout + << " satisfy_condition_delay: " << param.satisfy_condition_delay + << " expected_result: " + << (param.expected_result ? "true" : "false") + << " expected_delay: " << param.expected_delay; +} + +std::string FormatString(const TimeoutTestParam ¶m) { + std::ostringstream os; + os << param; + return os.str(); +} + +// Like `thread::Executor::ScheduleAt` except: +// a) Delays zero or negative are executed immediately in the current thread. +// b) Infinite delays are never scheduled. +// c) Calls this test's `ScheduleAt` helper instead of using `pool` directly. +static void RunAfterDelay(absl::Duration delay, + absl::synchronization_internal::ThreadPool *pool, + const std::function<void()> &callback) { + if (delay <= absl::ZeroDuration()) { + callback(); // immediate + } else if (delay != absl::InfiniteDuration()) { + ScheduleAfter(pool, delay, callback); } +} - bool ReaderLockWhenWithTimeout(absl::Duration timeout) { - bool b = use_deadline_ - ? mu_.ReaderLockWhenWithDeadline(c_, absl::Now() + timeout) - : mu_.ReaderLockWhenWithTimeout(c_, timeout); - mu_.ReaderUnlock(); - return b; - } +class TimeoutTest : public ::testing::Test, + public ::testing::WithParamInterface<TimeoutTestParam> {}; - void Await() { - absl::MutexLock lock(&mu_); - mu_.Await(c_); - } +std::vector<TimeoutTestParam> MakeTimeoutTestParamValues() { + // The `finite` delay is a finite, relatively short, delay. We make it larger + // than our allowed scheduling delay (slop factor) to avoid confusion when + // diagnosing test failures. The other constants here have clear meanings. + const absl::Duration finite = 3 * TimeoutTestAllowedSchedulingDelay(); + const absl::Duration never = absl::InfiniteDuration(); + const absl::Duration negative = -absl::InfiniteDuration(); + const absl::Duration immediate = absl::ZeroDuration(); - void Signal(bool v) { - absl::MutexLock lock(&mu_); - b_ = v; - cv_.Signal(); + // Every test case is run twice; once using the absolute deadline API and once + // using the relative timeout API. + std::vector<TimeoutTestParam> values; + for (bool use_absolute_deadline : {false, true}) { + // Tests with a negative timeout (deadline in the past), which should + // immediately return current state of the condition. + + // The condition is already true: + values.push_back(TimeoutTestParam{ + __FILE__, __LINE__, use_absolute_deadline, + negative, // wait_timeout + immediate, // satisfy_condition_delay + true, // expected_result + immediate, // expected_delay + }); + + // The condition becomes true, but the timeout has already expired: + values.push_back(TimeoutTestParam{ + __FILE__, __LINE__, use_absolute_deadline, + negative, // wait_timeout + finite, // satisfy_condition_delay + false, // expected_result + immediate // expected_delay + }); + + // The condition never becomes true: + values.push_back(TimeoutTestParam{ + __FILE__, __LINE__, use_absolute_deadline, + negative, // wait_timeout + never, // satisfy_condition_delay + false, // expected_result + immediate // expected_delay + }); + + // Tests with an infinite timeout (deadline in the infinite future), which + // should only return when the condition becomes true. + + // The condition is already true: + values.push_back(TimeoutTestParam{ + __FILE__, __LINE__, use_absolute_deadline, + never, // wait_timeout + immediate, // satisfy_condition_delay + true, // expected_result + immediate // expected_delay + }); + + // The condition becomes true before the (infinite) expiry: + values.push_back(TimeoutTestParam{ + __FILE__, __LINE__, use_absolute_deadline, + never, // wait_timeout + finite, // satisfy_condition_delay + true, // expected_result + finite, // expected_delay + }); + + // Tests with a (small) finite timeout (deadline soon), with the condition + // becoming true both before and after its expiry. + + // The condition is already true: + values.push_back(TimeoutTestParam{ + __FILE__, __LINE__, use_absolute_deadline, + never, // wait_timeout + immediate, // satisfy_condition_delay + true, // expected_result + immediate // expected_delay + }); + + // The condition becomes true before the expiry: + values.push_back(TimeoutTestParam{ + __FILE__, __LINE__, use_absolute_deadline, + finite * 2, // wait_timeout + finite, // satisfy_condition_delay + true, // expected_result + finite // expected_delay + }); + + // The condition becomes true, but the timeout has already expired: + values.push_back(TimeoutTestParam{ + __FILE__, __LINE__, use_absolute_deadline, + finite, // wait_timeout + finite * 2, // satisfy_condition_delay + false, // expected_result + finite // expected_delay + }); + + // The condition never becomes true: + values.push_back(TimeoutTestParam{ + __FILE__, __LINE__, use_absolute_deadline, + finite, // wait_timeout + never, // satisfy_condition_delay + false, // expected_result + finite // expected_delay + }); } - - bool WaitWithTimeout(absl::Duration timeout) { - absl::MutexLock lock(&mu_); - absl::Time deadline = absl::Now() + timeout; - if (use_deadline_) { - while (!b_ && !cv_.WaitWithDeadline(&mu_, deadline)) { - } - } else { - while (!b_ && !cv_.WaitWithTimeout(&mu_, timeout)) { - timeout = deadline - absl::Now(); // recompute timeout - } + return values; +} + +// Instantiate `TimeoutTest` with `MakeTimeoutTestParamValues()`. +INSTANTIATE_TEST_CASE_P(All, TimeoutTest, + testing::ValuesIn(MakeTimeoutTestParamValues())); + +TEST_P(TimeoutTest, Await) { + const TimeoutTestParam params = GetParam(); + ABSL_RAW_LOG(INFO, "Params: %s", FormatString(params).c_str()); + + // Because this test asserts bounds on scheduling delays it is flaky. To + // compensate it loops forever until it passes. Failures express as test + // timeouts, in which case the test log can be used to diagnose the issue. + for (int attempt = 1;; ++attempt) { + ABSL_RAW_LOG(INFO, "Attempt %d", attempt); + + absl::Mutex mu; + bool value = false; // condition value (under mu) + + std::unique_ptr<absl::synchronization_internal::ThreadPool> pool = + CreateDefaultPool(); + RunAfterDelay(params.satisfy_condition_delay, pool.get(), [&] { + absl::MutexLock l(&mu); + value = true; + }); + + absl::MutexLock lock(&mu); + absl::Time start_time = absl::Now(); + absl::Condition cond(&value); + bool result = + params.use_absolute_deadline + ? mu.AwaitWithDeadline(cond, start_time + params.wait_timeout) + : mu.AwaitWithTimeout(cond, params.wait_timeout); + if (DelayIsWithinBounds(params.expected_delay, absl::Now() - start_time)) { + EXPECT_EQ(params.expected_result, result); + break; } - return b_; - } - - void Wait() { - absl::MutexLock lock(&mu_); - while (!b_) cv_.Wait(&mu_); - } - - private: - const bool use_deadline_; - - bool b_; - absl::Condition c_; - absl::CondVar cv_; - absl::Mutex mu_; -}; - -class OperationTimer { - public: - OperationTimer() : start_(absl::Now()) {} - absl::Duration Get() const { return absl::Now() - start_; } - - private: - const absl::Time start_; -}; - -static void CheckResults(bool exp_result, bool act_result, - absl::Duration exp_duration, - absl::Duration act_duration) { - ABSL_RAW_CHECK(exp_result == act_result, "CheckResults failed"); - // Allow for some worse-case scheduling delay and clock skew. - if ((exp_duration - absl::Milliseconds(40) > act_duration) || - (exp_duration + absl::Milliseconds(150) < act_duration)) { - ABSL_RAW_LOG(FATAL, "CheckResults failed: operation took %s, expected %s", - absl::FormatDuration(act_duration).c_str(), - absl::FormatDuration(exp_duration).c_str()); } } -static void TestAwaitTimeout(Cond *cp, absl::Duration timeout, bool exp_result, - absl::Duration exp_duration) { - OperationTimer t; - bool act_result = cp->AwaitWithTimeout(timeout); - CheckResults(exp_result, act_result, exp_duration, t.Get()); -} - -static void TestLockWhenTimeout(Cond *cp, absl::Duration timeout, - bool exp_result, absl::Duration exp_duration) { - OperationTimer t; - bool act_result = cp->LockWhenWithTimeout(timeout); - CheckResults(exp_result, act_result, exp_duration, t.Get()); -} +TEST_P(TimeoutTest, LockWhen) { + const TimeoutTestParam params = GetParam(); + ABSL_RAW_LOG(INFO, "Params: %s", FormatString(params).c_str()); + + // Because this test asserts bounds on scheduling delays it is flaky. To + // compensate it loops forever until it passes. Failures express as test + // timeouts, in which case the test log can be used to diagnose the issue. + for (int attempt = 1;; ++attempt) { + ABSL_RAW_LOG(INFO, "Attempt %d", attempt); + + absl::Mutex mu; + bool value = false; // condition value (under mu) + + std::unique_ptr<absl::synchronization_internal::ThreadPool> pool = + CreateDefaultPool(); + RunAfterDelay(params.satisfy_condition_delay, pool.get(), [&] { + absl::MutexLock l(&mu); + value = true; + }); + + absl::Time start_time = absl::Now(); + absl::Condition cond(&value); + bool result = + params.use_absolute_deadline + ? mu.LockWhenWithDeadline(cond, start_time + params.wait_timeout) + : mu.LockWhenWithTimeout(cond, params.wait_timeout); + mu.Unlock(); -static void TestReaderLockWhenTimeout(Cond *cp, absl::Duration timeout, - bool exp_result, - absl::Duration exp_duration) { - OperationTimer t; - bool act_result = cp->ReaderLockWhenWithTimeout(timeout); - CheckResults(exp_result, act_result, exp_duration, t.Get()); + if (DelayIsWithinBounds(params.expected_delay, absl::Now() - start_time)) { + EXPECT_EQ(params.expected_result, result); + break; + } + } } -static void TestWaitTimeout(Cond *cp, absl::Duration timeout, bool exp_result, - absl::Duration exp_duration) { - OperationTimer t; - bool act_result = cp->WaitWithTimeout(timeout); - CheckResults(exp_result, act_result, exp_duration, t.Get()); +TEST_P(TimeoutTest, ReaderLockWhen) { + const TimeoutTestParam params = GetParam(); + ABSL_RAW_LOG(INFO, "Params: %s", FormatString(params).c_str()); + + // Because this test asserts bounds on scheduling delays it is flaky. To + // compensate it loops forever until it passes. Failures express as test + // timeouts, in which case the test log can be used to diagnose the issue. + for (int attempt = 0;; ++attempt) { + ABSL_RAW_LOG(INFO, "Attempt %d", attempt); + + absl::Mutex mu; + bool value = false; // condition value (under mu) + + std::unique_ptr<absl::synchronization_internal::ThreadPool> pool = + CreateDefaultPool(); + RunAfterDelay(params.satisfy_condition_delay, pool.get(), [&] { + absl::MutexLock l(&mu); + value = true; + }); + + absl::Time start_time = absl::Now(); + bool result = + params.use_absolute_deadline + ? mu.ReaderLockWhenWithDeadline(absl::Condition(&value), + start_time + params.wait_timeout) + : mu.ReaderLockWhenWithTimeout(absl::Condition(&value), + params.wait_timeout); + mu.ReaderUnlock(); + + if (DelayIsWithinBounds(params.expected_delay, absl::Now() - start_time)) { + EXPECT_EQ(params.expected_result, result); + break; + } + } } -// Tests with a negative timeout (deadline in the past), which should -// immediately return the current state of the condition. -static void TestNegativeTimeouts(absl::synchronization_internal::ThreadPool *tp, - Cond *cp) { - const absl::Duration negative = -absl::InfiniteDuration(); - const absl::Duration immediate = absl::ZeroDuration(); - - // The condition is already true: - cp->Set(true); - TestAwaitTimeout(cp, negative, true, immediate); - TestLockWhenTimeout(cp, negative, true, immediate); - TestReaderLockWhenTimeout(cp, negative, true, immediate); - TestWaitTimeout(cp, negative, true, immediate); - - // The condition becomes true, but the timeout has already expired: - const absl::Duration delay = absl::Milliseconds(200); - cp->Set(false); - ScheduleAfter(tp, std::bind(&Cond::Set, cp, true), 3 * delay); - TestAwaitTimeout(cp, negative, false, immediate); - TestLockWhenTimeout(cp, negative, false, immediate); - TestReaderLockWhenTimeout(cp, negative, false, immediate); - cp->Await(); // wait for the scheduled Set() to complete - cp->Set(false); - ScheduleAfter(tp, std::bind(&Cond::Signal, cp, true), delay); - TestWaitTimeout(cp, negative, false, immediate); - cp->Wait(); // wait for the scheduled Signal() to complete - - // The condition never becomes true: - cp->Set(false); - TestAwaitTimeout(cp, negative, false, immediate); - TestLockWhenTimeout(cp, negative, false, immediate); - TestReaderLockWhenTimeout(cp, negative, false, immediate); - TestWaitTimeout(cp, negative, false, immediate); -} - -// Tests with an infinite timeout (deadline in the infinite future), which -// should only return when the condition becomes true. -static void TestInfiniteTimeouts(absl::synchronization_internal::ThreadPool *tp, - Cond *cp) { - const absl::Duration infinite = absl::InfiniteDuration(); - const absl::Duration immediate = absl::ZeroDuration(); - - // The condition is already true: - cp->Set(true); - TestAwaitTimeout(cp, infinite, true, immediate); - TestLockWhenTimeout(cp, infinite, true, immediate); - TestReaderLockWhenTimeout(cp, infinite, true, immediate); - TestWaitTimeout(cp, infinite, true, immediate); - - // The condition becomes true before the (infinite) expiry: - const absl::Duration delay = absl::Milliseconds(200); - cp->Set(false); - ScheduleAfter(tp, std::bind(&Cond::Set, cp, true), delay); - TestAwaitTimeout(cp, infinite, true, delay); - cp->Set(false); - ScheduleAfter(tp, std::bind(&Cond::Set, cp, true), delay); - TestLockWhenTimeout(cp, infinite, true, delay); - cp->Set(false); - ScheduleAfter(tp, std::bind(&Cond::Set, cp, true), delay); - TestReaderLockWhenTimeout(cp, infinite, true, delay); - cp->Set(false); - ScheduleAfter(tp, std::bind(&Cond::Signal, cp, true), delay); - TestWaitTimeout(cp, infinite, true, delay); -} - -// Tests with a (small) finite timeout (deadline soon), with the condition -// becoming true both before and after its expiry. -static void TestFiniteTimeouts(absl::synchronization_internal::ThreadPool *tp, - Cond *cp) { - const absl::Duration finite = absl::Milliseconds(400); - const absl::Duration immediate = absl::ZeroDuration(); +TEST_P(TimeoutTest, Wait) { + const TimeoutTestParam params = GetParam(); + ABSL_RAW_LOG(INFO, "Params: %s", FormatString(params).c_str()); + + // Because this test asserts bounds on scheduling delays it is flaky. To + // compensate it loops forever until it passes. Failures express as test + // timeouts, in which case the test log can be used to diagnose the issue. + for (int attempt = 0;; ++attempt) { + ABSL_RAW_LOG(INFO, "Attempt %d", attempt); + + absl::Mutex mu; + bool value = false; // condition value (under mu) + absl::CondVar cv; // signals a change of `value` + + std::unique_ptr<absl::synchronization_internal::ThreadPool> pool = + CreateDefaultPool(); + RunAfterDelay(params.satisfy_condition_delay, pool.get(), [&] { + absl::MutexLock l(&mu); + value = true; + cv.Signal(); + }); + + absl::MutexLock lock(&mu); + absl::Time start_time = absl::Now(); + absl::Duration timeout = params.wait_timeout; + absl::Time deadline = start_time + timeout; + while (!value) { + if (params.use_absolute_deadline ? cv.WaitWithDeadline(&mu, deadline) + : cv.WaitWithTimeout(&mu, timeout)) { + break; // deadline/timeout exceeded + } + timeout = deadline - absl::Now(); // recompute + } + bool result = value; // note: `mu` is still held - // The condition is already true: - cp->Set(true); - TestAwaitTimeout(cp, finite, true, immediate); - TestLockWhenTimeout(cp, finite, true, immediate); - TestReaderLockWhenTimeout(cp, finite, true, immediate); - TestWaitTimeout(cp, finite, true, immediate); - - // The condition becomes true before the expiry: - const absl::Duration delay1 = finite / 2; - cp->Set(false); - ScheduleAfter(tp, std::bind(&Cond::Set, cp, true), delay1); - TestAwaitTimeout(cp, finite, true, delay1); - cp->Set(false); - ScheduleAfter(tp, std::bind(&Cond::Set, cp, true), delay1); - TestLockWhenTimeout(cp, finite, true, delay1); - cp->Set(false); - ScheduleAfter(tp, std::bind(&Cond::Set, cp, true), delay1); - TestReaderLockWhenTimeout(cp, finite, true, delay1); - cp->Set(false); - ScheduleAfter(tp, std::bind(&Cond::Signal, cp, true), delay1); - TestWaitTimeout(cp, finite, true, delay1); - - // The condition becomes true, but the timeout has already expired: - const absl::Duration delay2 = finite * 2; - cp->Set(false); - ScheduleAfter(tp, std::bind(&Cond::Set, cp, true), 3 * delay2); - TestAwaitTimeout(cp, finite, false, finite); - TestLockWhenTimeout(cp, finite, false, finite); - TestReaderLockWhenTimeout(cp, finite, false, finite); - cp->Await(); // wait for the scheduled Set() to complete - cp->Set(false); - ScheduleAfter(tp, std::bind(&Cond::Signal, cp, true), delay2); - TestWaitTimeout(cp, finite, false, finite); - cp->Wait(); // wait for the scheduled Signal() to complete - - // The condition never becomes true: - cp->Set(false); - TestAwaitTimeout(cp, finite, false, finite); - TestLockWhenTimeout(cp, finite, false, finite); - TestReaderLockWhenTimeout(cp, finite, false, finite); - TestWaitTimeout(cp, finite, false, finite); -} - -TEST(Mutex, Timeouts) { - auto tp = CreateDefaultPool(); - for (bool use_deadline : {false, true}) { - Cond cond(use_deadline); - TestNegativeTimeouts(tp.get(), &cond); - TestInfiniteTimeouts(tp.get(), &cond); - TestFiniteTimeouts(tp.get(), &cond); + if (DelayIsWithinBounds(params.expected_delay, absl::Now() - start_time)) { + EXPECT_EQ(params.expected_result, result); + break; + } } } diff --git a/absl/time/clock_test.cc b/absl/time/clock_test.cc index f143c036..707166d0 100644 --- a/absl/time/clock_test.cc +++ b/absl/time/clock_test.cc @@ -35,36 +35,84 @@ TEST(Time, Now) { EXPECT_GE(after, now); } -TEST(SleepForTest, BasicSanity) { - absl::Duration sleep_time = absl::Milliseconds(2500); - absl::Time start = absl::Now(); - absl::SleepFor(sleep_time); - absl::Time end = absl::Now(); - EXPECT_LE(sleep_time - absl::Milliseconds(100), end - start); - EXPECT_GE(sleep_time + absl::Milliseconds(200), end - start); -} +enum class AlarmPolicy { kWithoutAlarm, kWithAlarm }; -#ifdef ABSL_HAVE_ALARM -// Helper for test SleepFor. +#if defined(ABSL_HAVE_ALARM) bool alarm_handler_invoked = false; + void AlarmHandler(int signo) { ASSERT_EQ(signo, SIGALRM); alarm_handler_invoked = true; } +#endif + +// Does SleepFor(d) take between lower_bound and upper_bound at least +// once between now and (now + timeout)? If requested (and supported), +// add an alarm for the middle of the sleep period and expect it to fire. +bool SleepForBounded(absl::Duration d, absl::Duration lower_bound, + absl::Duration upper_bound, absl::Duration timeout, + AlarmPolicy alarm_policy, int* attempts) { + const absl::Time deadline = absl::Now() + timeout; + while (absl::Now() < deadline) { +#if defined(ABSL_HAVE_ALARM) + sig_t old_alarm = SIG_DFL; + if (alarm_policy == AlarmPolicy::kWithAlarm) { + alarm_handler_invoked = false; + old_alarm = signal(SIGALRM, AlarmHandler); + alarm(absl::ToInt64Seconds(d / 2)); + } +#else + EXPECT_EQ(alarm_policy, AlarmPolicy::kWithoutAlarm); +#endif + ++*attempts; + absl::Time start = absl::Now(); + absl::SleepFor(d); + absl::Duration actual = absl::Now() - start; +#if defined(ABSL_HAVE_ALARM) + if (alarm_policy == AlarmPolicy::kWithAlarm) { + signal(SIGALRM, old_alarm); + if (!alarm_handler_invoked) continue; + } +#endif + if (lower_bound <= actual && actual <= upper_bound) { + return true; // yes, the SleepFor() was correctly bounded + } + } + return false; +} -TEST(SleepForTest, AlarmSupport) { - alarm_handler_invoked = false; - sig_t old_alarm = signal(SIGALRM, AlarmHandler); - alarm(2); - absl::Duration sleep_time = absl::Milliseconds(3500); - absl::Time start = absl::Now(); - absl::SleepFor(sleep_time); - absl::Time end = absl::Now(); - EXPECT_TRUE(alarm_handler_invoked); - EXPECT_LE(sleep_time - absl::Milliseconds(100), end - start); - EXPECT_GE(sleep_time + absl::Milliseconds(200), end - start); - signal(SIGALRM, old_alarm); +testing::AssertionResult AssertSleepForBounded(absl::Duration d, + absl::Duration early, + absl::Duration late, + absl::Duration timeout, + AlarmPolicy alarm_policy) { + const absl::Duration lower_bound = d - early; + const absl::Duration upper_bound = d + late; + int attempts = 0; + if (SleepForBounded(d, lower_bound, upper_bound, timeout, alarm_policy, + &attempts)) { + return testing::AssertionSuccess(); + } + return testing::AssertionFailure() + << "SleepFor(" << d << ") did not return within [" << lower_bound + << ":" << upper_bound << "] in " << attempts << " attempt" + << (attempts == 1 ? "" : "s") << " over " << timeout + << (alarm_policy == AlarmPolicy::kWithAlarm ? " with" : " without") + << " an alarm"; +} + +// Tests that SleepFor() returns neither too early nor too late. +TEST(SleepFor, Bounded) { + const absl::Duration d = absl::Milliseconds(2500); + const absl::Duration early = absl::Milliseconds(100); + const absl::Duration late = absl::Milliseconds(300); + const absl::Duration timeout = 48 * d; + EXPECT_TRUE(AssertSleepForBounded(d, early, late, timeout, + AlarmPolicy::kWithoutAlarm)); +#if defined(ABSL_HAVE_ALARM) + EXPECT_TRUE(AssertSleepForBounded(d, early, late, timeout, + AlarmPolicy::kWithAlarm)); +#endif } -#endif // ABSL_HAVE_ALARM } // namespace 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 d52eddcd..2362a4f4 100644 --- a/absl/time/internal/cctz/include/cctz/civil_time_detail.h +++ b/absl/time/internal/cctz/include/cctz/civil_time_detail.h @@ -403,20 +403,16 @@ class civil_time { } // Binary arithmetic operators. - inline friend CONSTEXPR_M civil_time operator+(civil_time a, - diff_t n) noexcept { + friend CONSTEXPR_F civil_time operator+(civil_time a, diff_t n) noexcept { return a += n; } - inline friend CONSTEXPR_M civil_time operator+(diff_t n, - civil_time a) noexcept { + friend CONSTEXPR_F civil_time operator+(diff_t n, civil_time a) noexcept { return a += n; } - inline friend CONSTEXPR_M civil_time operator-(civil_time a, - diff_t n) noexcept { + friend CONSTEXPR_F civil_time operator-(civil_time a, diff_t n) noexcept { return a -= n; } - inline friend CONSTEXPR_M diff_t operator-(const civil_time& lhs, - const civil_time& rhs) noexcept { + friend CONSTEXPR_F diff_t operator-(civil_time lhs, civil_time rhs) noexcept { return difference(T{}, lhs.f_, rhs.f_); } @@ -434,8 +430,8 @@ class civil_time { // Disallows difference between differently aligned types. // auto n = civil_day(...) - civil_hour(...); // would be confusing. -template <typename Tag1, typename Tag2> -CONSTEXPR_F diff_t operator-(civil_time<Tag1>, civil_time<Tag2>) = delete; +template <typename T, typename U> +CONSTEXPR_F diff_t operator-(civil_time<T>, civil_time<U>) = delete; using civil_year = civil_time<year_tag>; using civil_month = civil_time<month_tag>; diff --git a/absl/time/internal/cctz/include/cctz/time_zone.h b/absl/time/internal/cctz/include/cctz/time_zone.h index 55804ba6..0b9764ea 100644 --- a/absl/time/internal/cctz/include/cctz/time_zone.h +++ b/absl/time/internal/cctz/include/cctz/time_zone.h @@ -119,7 +119,7 @@ class time_zone { // of the given civil-time argument, and the pre, trans, and post // members will give the absolute time answers using the pre-transition // offset, the transition point itself, and the post-transition offset, - // respectively (all three times are equal if kind == UNIQUE). If any + // respectively (all three times are equal if kind == UNIQUE). If any // of these three absolute times is outside the representable range of a // time_point<seconds> the field is set to its maximum/minimum value. // @@ -159,17 +159,79 @@ class time_zone { }; civil_lookup lookup(const civil_second& cs) const; + // Finds the time of the next/previous offset change in this time zone. + // + // By definition, next_transition(tp, &trans) returns false when tp has + // its maximum value, and prev_transition(tp, &trans) returns false + // when tp has its minimum value. If the zone has no transitions, the + // result will also be false no matter what the argument. + // + // Otherwise, when tp has its minimum value, next_transition(tp, &trans) + // returns true and sets trans to the first recorded transition. Chains + // of calls to next_transition()/prev_transition() will eventually return + // false, but it is unspecified exactly when next_transition(tp, &trans) + // jumps to false, or what time is set by prev_transition(tp, &trans) for + // a very distant tp. + // + // Note: Enumeration of time-zone transitions is for informational purposes + // only. Modern time-related code should not care about when offset changes + // occur. + // + // Example: + // cctz::time_zone nyc; + // if (!cctz::load_time_zone("America/New_York", &nyc)) { ... } + // const auto now = std::chrono::system_clock::now(); + // auto tp = cctz::time_point<cctz::seconds>::min(); + // cctz::time_zone::civil_transition trans; + // while (tp <= now && nyc.next_transition(tp, &trans)) { + // // transition: trans.from -> trans.to + // tp = nyc.lookup(trans.to).trans; + // } + struct civil_transition { + civil_second from; // the civil time we jump from + civil_second to; // the civil time we jump to + }; + bool next_transition(const time_point<seconds>& tp, + civil_transition* trans) const; + template <typename D> + bool next_transition(const time_point<D>& tp, + civil_transition* trans) const { + return next_transition(detail::split_seconds(tp).first, trans); + } + bool prev_transition(const time_point<seconds>& tp, + civil_transition* trans) const; + template <typename D> + bool prev_transition(const time_point<D>& tp, + civil_transition* trans) const { + return prev_transition(detail::split_seconds(tp).first, trans); + } + + // version() and description() provide additional information about the + // time zone. The content of each of the returned strings is unspecified, + // however, when the IANA Time Zone Database is the underlying data source + // the version() std::string will be in the familar form (e.g, "2018e") or + // empty when unavailable. + // + // Note: These functions are for informational or testing purposes only. + std::string version() const; // empty when unknown + std::string description() const; + + // Relational operators. + friend bool operator==(time_zone lhs, time_zone rhs) { + return &lhs.effective_impl() == &rhs.effective_impl(); + } + friend bool operator!=(time_zone lhs, time_zone rhs) { + return !(lhs == rhs); + } + class Impl; private: explicit time_zone(const Impl* impl) : impl_(impl) {} + const Impl& effective_impl() const; // handles implicit UTC const Impl* impl_; }; -// Relational operators. -bool operator==(time_zone lhs, time_zone rhs); -inline bool operator!=(time_zone lhs, time_zone rhs) { return !(lhs == rhs); } - // Loads the named time zone. May perform I/O on the initial load. // If the name is invalid, or some other kind of error occurs, returns // false and "*tz" is set to the UTC time zone. @@ -184,6 +246,7 @@ time_zone utc_time_zone(); time_zone fixed_time_zone(const seconds& offset); // Returns a time zone representing the local time zone. Falls back to UTC. +// Note: local_time_zone.name() may only be something like "localtime". time_zone local_time_zone(); // Returns the civil time (cctz::civil_second) within the given time zone at @@ -227,7 +290,7 @@ bool parse(const std::string&, const std::string&, const time_zone&, // - %E*f - Fractional seconds with full precision (a literal '*') // - %E4Y - Four-character years (-999 ... -001, 0000, 0001 ... 9999) // -// Note that %E0S behaves like %S, and %E0f produces no characters. In +// Note that %E0S behaves like %S, and %E0f produces no characters. In // contrast %E*f always produces at least one digit, which may be '0'. // // Note that %Y produces as many characters as it takes to fully render the @@ -254,7 +317,7 @@ inline std::string format(const std::string& fmt, const time_point<D>& tp, // Parses an input std::string according to the provided format std::string and // returns the corresponding time_point. Uses strftime()-like formatting // options, with the same extensions as cctz::format(), but with the -// exceptions that %E#S is interpreted as %E*S, and %E#f as %E*f. %Ez +// exceptions that %E#S is interpreted as %E*S, and %E#f as %E*f. %Ez // and %E*z also accept the same inputs. // // %Y consumes as many numeric characters as it can, so the matching data diff --git a/absl/time/internal/cctz/include/cctz/zone_info_source.h b/absl/time/internal/cctz/include/cctz/zone_info_source.h index 4d9d8f87..20a76979 100644 --- a/absl/time/internal/cctz/include/cctz/zone_info_source.h +++ b/absl/time/internal/cctz/include/cctz/zone_info_source.h @@ -31,6 +31,11 @@ class ZoneInfoSource { virtual std::size_t Read(void* ptr, std::size_t size) = 0; // like fread() virtual int Skip(std::size_t offset) = 0; // like fseek() + + // Until the zoneinfo data supports versioning information, we provide + // a way for a ZoneInfoSource to indicate it out-of-band. The default + // implementation returns an empty std::string. + virtual std::string Version() const; }; } // namespace cctz diff --git a/absl/time/internal/cctz/src/cctz_benchmark.cc b/absl/time/internal/cctz/src/cctz_benchmark.cc index f13cb4ee..c97df78c 100644 --- a/absl/time/internal/cctz/src/cctz_benchmark.cc +++ b/absl/time/internal/cctz/src/cctz_benchmark.cc @@ -754,23 +754,21 @@ void BM_Zone_LoadAllTimeZonesCached(benchmark::State& state) { } BENCHMARK(BM_Zone_LoadAllTimeZonesCached); -void BM_Zone_TimeZoneImplGetImplicit(benchmark::State& state) { +void BM_Zone_TimeZoneEqualityImplicit(benchmark::State& state) { cctz::time_zone tz; // implicit UTC - cctz::time_zone::Impl::get(tz); while (state.KeepRunning()) { - cctz::time_zone::Impl::get(tz); + benchmark::DoNotOptimize(tz == tz); } } -BENCHMARK(BM_Zone_TimeZoneImplGetImplicit); +BENCHMARK(BM_Zone_TimeZoneEqualityImplicit); -void BM_Zone_TimeZoneImplGetExplicit(benchmark::State& state) { +void BM_Zone_TimeZoneEqualityExplicit(benchmark::State& state) { cctz::time_zone tz = cctz::utc_time_zone(); // explicit UTC - cctz::time_zone::Impl::get(tz); while (state.KeepRunning()) { - cctz::time_zone::Impl::get(tz); + benchmark::DoNotOptimize(tz == tz); } } -BENCHMARK(BM_Zone_TimeZoneImplGetExplicit); +BENCHMARK(BM_Zone_TimeZoneEqualityExplicit); void BM_Zone_UTCTimeZone(benchmark::State& state) { cctz::time_zone tz; diff --git a/absl/time/internal/cctz/src/time_zone_format.cc b/absl/time/internal/cctz/src/time_zone_format.cc index 592ab7d3..1b023848 100644 --- a/absl/time/internal/cctz/src/time_zone_format.cc +++ b/absl/time/internal/cctz/src/time_zone_format.cc @@ -141,6 +141,9 @@ char* Format02d(char* ep, int v) { // Formats a UTC offset, like +00:00. char* FormatOffset(char* ep, int offset, const char* mode) { + // TODO: Follow the RFC3339 "Unknown Local Offset Convention" and + // generate a "negative zero" when we're formatting a zero offset + // as the result of a failed load_time_zone(). char sign = '+'; if (offset < 0) { offset = -offset; // bounded by 24h so no overflow diff --git a/absl/time/internal/cctz/src/time_zone_format_test.cc b/absl/time/internal/cctz/src/time_zone_format_test.cc index 33c23984..a90dda76 100644 --- a/absl/time/internal/cctz/src/time_zone_format_test.cc +++ b/absl/time/internal/cctz/src/time_zone_format_test.cc @@ -64,6 +64,17 @@ void TestFormatSpecifier(time_point<D> tp, time_zone tz, const std::string& fmt, EXPECT_EQ("xxx " + ans + " yyy", format("xxx " + fmt + " yyy", tp, tz)); } +// These tests sometimes run on platforms that have zoneinfo data so old +// that the transition we are attempting to check does not exist, most +// notably Android emulators. Fortunately, AndroidZoneInfoSource supports +// time_zone::version() so, in cases where we've learned that it matters, +// we can make the check conditionally. +int VersionCmp(time_zone tz, const std::string& target) { + std::string version = tz.version(); + if (version.empty() && !target.empty()) return 1; // unknown > known + return version.compare(target); +} + } // namespace // @@ -453,8 +464,8 @@ TEST(Format, ExtendedSecondOffset) { EXPECT_TRUE(load_time_zone("America/New_York", &tz)); tp = convert(civil_second(1883, 11, 18, 16, 59, 59), utc); if (tz.lookup(tp).offset == -5 * 60 * 60) { - // We're likely dealing with zoneinfo that doesn't support really old - // timestamps, so America/New_York never looks to be on local mean time. + // It looks like the tzdata is only 32 bit (probably macOS), + // which bottoms out at 1901-12-13T20:45:52+00:00. } else { TestFormatSpecifier(tp, tz, "%E*z", "-04:56:02"); TestFormatSpecifier(tp, tz, "%Ez", "-04:56"); @@ -464,12 +475,10 @@ TEST(Format, ExtendedSecondOffset) { EXPECT_TRUE(load_time_zone("Europe/Moscow", &tz)); tp = convert(civil_second(1919, 6, 30, 23, 59, 59), utc); -#if defined(__ANDROID__) && __ANDROID_API__ < 25 - // Only Android 'N'.1 and beyond have this tz2016g transition. -#else - TestFormatSpecifier(tp, tz, "%E*z", "+04:31:19"); - TestFormatSpecifier(tp, tz, "%Ez", "+04:31"); -#endif + if (VersionCmp(tz, "2016g") >= 0) { + TestFormatSpecifier(tp, tz, "%E*z", "+04:31:19"); + TestFormatSpecifier(tp, tz, "%Ez", "+04:31"); + } tp += chrono::seconds(1); TestFormatSpecifier(tp, tz, "%E*z", "+04:00:00"); } diff --git a/absl/time/internal/cctz/src/time_zone_if.h b/absl/time/internal/cctz/src/time_zone_if.h index f10972ae..e4bd3866 100644 --- a/absl/time/internal/cctz/src/time_zone_if.h +++ b/absl/time/internal/cctz/src/time_zone_if.h @@ -41,9 +41,13 @@ class TimeZoneIf { virtual time_zone::civil_lookup MakeTime( const civil_second& cs) const = 0; + virtual bool NextTransition(const time_point<seconds>& tp, + time_zone::civil_transition* trans) const = 0; + virtual bool PrevTransition(const time_point<seconds>& tp, + time_zone::civil_transition* trans) const = 0; + + virtual std::string Version() const = 0; virtual std::string Description() const = 0; - virtual bool NextTransition(time_point<seconds>* tp) const = 0; - virtual bool PrevTransition(time_point<seconds>* tp) const = 0; protected: TimeZoneIf() {} diff --git a/absl/time/internal/cctz/src/time_zone_impl.cc b/absl/time/internal/cctz/src/time_zone_impl.cc index eb96c7ef..3062ccd3 100644 --- a/absl/time/internal/cctz/src/time_zone_impl.cc +++ b/absl/time/internal/cctz/src/time_zone_impl.cc @@ -83,15 +83,6 @@ bool time_zone::Impl::LoadTimeZone(const std::string& name, time_zone* tz) { return impl != utc_impl; } -const time_zone::Impl& time_zone::Impl::get(const time_zone& tz) { - if (tz.impl_ == nullptr) { - // Dereferencing an implicit-UTC time_zone is expected to be - // rare, so we don't mind paying a small synchronization cost. - return *UTCImpl(); - } - return *tz.impl_; -} - void time_zone::Impl::ClearTimeZoneMapTestOnly() { std::lock_guard<std::mutex> lock(time_zone_mutex); if (time_zone_map != nullptr) { diff --git a/absl/time/internal/cctz/src/time_zone_impl.h b/absl/time/internal/cctz/src/time_zone_impl.h index fef7f226..14965ef5 100644 --- a/absl/time/internal/cctz/src/time_zone_impl.h +++ b/absl/time/internal/cctz/src/time_zone_impl.h @@ -37,15 +37,15 @@ class time_zone::Impl { // some other kind of error occurs. Note that loading "UTC" never fails. static bool LoadTimeZone(const std::string& name, time_zone* tz); - // Dereferences the time_zone to obtain its Impl. - static const time_zone::Impl& get(const time_zone& tz); - // Clears the map of cached time zones. Primarily for use in benchmarks // that gauge the performance of loading/parsing the time-zone data. static void ClearTimeZoneMapTestOnly(); // The primary key is the time-zone ID (e.g., "America/New_York"). - const std::string& name() const { return name_; } + const std::string& Name() const { + // TODO: It would nice if the zoneinfo data included the zone name. + return name_; + } // Breaks a time_point down to civil-time components in this time zone. time_zone::absolute_lookup BreakTime(const time_point<seconds>& tp) const { @@ -59,28 +59,22 @@ class time_zone::Impl { return zone_->MakeTime(cs); } - // Returns an implementation-specific description of this time zone. - std::string Description() const { return zone_->Description(); } - // Finds the time of the next/previous offset change in this time zone. - // - // By definition, NextTransition(&tp) returns false when tp has its - // maximum value, and PrevTransition(&tp) returns false when tp has its - // mimimum value. If the zone has no transitions, the result will also - // be false no matter what the argument. - // - // Otherwise, when tp has its mimimum value, NextTransition(&tp) returns - // true and sets tp to the first recorded transition. Chains of calls - // to NextTransition()/PrevTransition() will eventually return false, - // but it is unspecified exactly when NextTransition(&tp) jumps to false, - // or what time is set by PrevTransition(&tp) for a very distant tp. - bool NextTransition(time_point<seconds>* tp) const { - return zone_->NextTransition(tp); + bool NextTransition(const time_point<seconds>& tp, + time_zone::civil_transition* trans) const { + return zone_->NextTransition(tp, trans); } - bool PrevTransition(time_point<seconds>* tp) const { - return zone_->PrevTransition(tp); + bool PrevTransition(const time_point<seconds>& tp, + time_zone::civil_transition* trans) const { + return zone_->PrevTransition(tp, trans); } + // Returns an implementation-defined version std::string for this time zone. + std::string Version() const { return zone_->Version(); } + + // Returns an implementation-defined description of this time zone. + std::string Description() const { return zone_->Description(); } + private: explicit Impl(const std::string& name); static const Impl* UTCImpl(); diff --git a/absl/time/internal/cctz/src/time_zone_info.cc b/absl/time/internal/cctz/src/time_zone_info.cc index cdd11810..bf73635d 100644 --- a/absl/time/internal/cctz/src/time_zone_info.cc +++ b/absl/time/internal/cctz/src/time_zone_info.cc @@ -186,14 +186,13 @@ bool TimeZoneInfo::ResetToBuiltinUTC(const seconds& offset) { tt.is_dst = false; tt.abbr_index = 0; - // We temporarily add some redundant, contemporary (2012 through 2021) + // We temporarily add some redundant, contemporary (2013 through 2023) // transitions for performance reasons. See TimeZoneInfo::LocalTime(). // TODO: Fix the performance issue and remove the extra transitions. transitions_.clear(); transitions_.reserve(12); for (const std::int_fast64_t unix_time : { -(1LL << 59), // BIG_BANG - 1325376000LL, // 2012-01-01T00:00:00+00:00 1356998400LL, // 2013-01-01T00:00:00+00:00 1388534400LL, // 2014-01-01T00:00:00+00:00 1420070400LL, // 2015-01-01T00:00:00+00:00 @@ -203,6 +202,8 @@ bool TimeZoneInfo::ResetToBuiltinUTC(const seconds& offset) { 1546300800LL, // 2019-01-01T00:00:00+00:00 1577836800LL, // 2020-01-01T00:00:00+00:00 1609459200LL, // 2021-01-01T00:00:00+00:00 + 1640995200LL, // 2022-01-01T00:00:00+00:00 + 1672531200LL, // 2023-01-01T00:00:00+00:00 2147483647LL, // 2^31 - 1 }) { Transition& tr(*transitions_.emplace(transitions_.end())); @@ -519,6 +520,13 @@ bool TimeZoneInfo::Load(const std::string& name, ZoneInfoSource* zip) { // We don't check for EOF so that we're forwards compatible. + // If we did not find version information during the standard loading + // process (as of tzh_version '3' that is unsupported), then ask the + // ZoneInfoSource for any out-of-bound version std::string it may be privy to. + if (version_.empty()) { + version_ = zip->Version(); + } + // Trim redundant transitions. zic may have added these to work around // differences between the glibc and reference implementations (see // zic.c:dontmerge) and the Qt library (see zic.c:WORK_AROUND_QTBUG_53071). @@ -605,6 +613,10 @@ class FileZoneInfoSource : public ZoneInfoSource { if (rc == 0) len_ -= offset; return rc; } + std::string Version() const override { + // TODO: It would nice if the zoneinfo data included the tzdb version. + return std::string(); + } protected: explicit FileZoneInfoSource( @@ -654,14 +666,15 @@ std::unique_ptr<ZoneInfoSource> FileZoneInfoSource::Open( return std::unique_ptr<ZoneInfoSource>(new FileZoneInfoSource(fp, length)); } -#if defined(__ANDROID__) class AndroidZoneInfoSource : public FileZoneInfoSource { public: static std::unique_ptr<ZoneInfoSource> Open(const std::string& name); + std::string Version() const override { return version_; } private: - explicit AndroidZoneInfoSource(FILE* fp, std::size_t len) - : FileZoneInfoSource(fp, len) {} + explicit AndroidZoneInfoSource(FILE* fp, std::size_t len, const char* vers) + : FileZoneInfoSource(fp, len), version_(vers) {} + std::string version_; }; std::unique_ptr<ZoneInfoSource> AndroidZoneInfoSource::Open( @@ -669,6 +682,7 @@ std::unique_ptr<ZoneInfoSource> AndroidZoneInfoSource::Open( // Use of the "file:" prefix is intended for testing purposes only. if (name.compare(0, 5, "file:") == 0) return Open(name.substr(5)); +#if defined(__ANDROID__) // See Android's libc/tzcode/bionic.cpp for additional information. for (const char* tzdata : {"/data/misc/zoneinfo/current/tzdata", "/system/usr/share/zoneinfo/tzdata"}) { @@ -678,6 +692,7 @@ std::unique_ptr<ZoneInfoSource> AndroidZoneInfoSource::Open( char hbuf[24]; // covers header.zonetab_offset too if (fread(hbuf, 1, sizeof(hbuf), fp.get()) != sizeof(hbuf)) continue; if (strncmp(hbuf, "tzdata", 6) != 0) continue; + const char* vers = (hbuf[11] == '\0') ? hbuf + 6 : ""; const std::int_fast32_t index_offset = Decode32(hbuf + 12); const std::int_fast32_t data_offset = Decode32(hbuf + 16); if (index_offset < 0 || data_offset < index_offset) continue; @@ -698,13 +713,13 @@ std::unique_ptr<ZoneInfoSource> AndroidZoneInfoSource::Open( if (strcmp(name.c_str(), ebuf) == 0) { if (fseek(fp.get(), static_cast<long>(start), SEEK_SET) != 0) break; return std::unique_ptr<ZoneInfoSource>(new AndroidZoneInfoSource( - fp.release(), static_cast<std::size_t>(length))); + fp.release(), static_cast<std::size_t>(length), vers)); } } } +#endif // __ANDROID__ return nullptr; } -#endif } // namespace @@ -722,9 +737,7 @@ bool TimeZoneInfo::Load(const std::string& name) { auto zip = cctz_extension::zone_info_source_factory( name, [](const std::string& name) -> std::unique_ptr<ZoneInfoSource> { if (auto zip = FileZoneInfoSource::Open(name)) return zip; -#if defined(__ANDROID__) if (auto zip = AndroidZoneInfoSource::Open(name)) return zip; -#endif return nullptr; }); return zip != nullptr && Load(name, zip.get()); @@ -885,17 +898,20 @@ time_zone::civil_lookup TimeZoneInfo::MakeTime(const civil_second& cs) const { return MakeUnique(tr->unix_time + (cs - tr->civil_sec)); } +std::string TimeZoneInfo::Version() const { + return version_; +} + std::string TimeZoneInfo::Description() const { std::ostringstream oss; - // TODO: It would nice if the zoneinfo data included the zone name. - // TODO: It would nice if the zoneinfo data included the tzdb version. oss << "#trans=" << transitions_.size(); oss << " #types=" << transition_types_.size(); oss << " spec='" << future_spec_ << "'"; return oss.str(); } -bool TimeZoneInfo::NextTransition(time_point<seconds>* tp) const { +bool TimeZoneInfo::NextTransition(const time_point<seconds>& tp, + time_zone::civil_transition* trans) const { if (transitions_.empty()) return false; const Transition* begin = &transitions_[0]; const Transition* end = begin + transitions_.size(); @@ -904,22 +920,24 @@ bool TimeZoneInfo::NextTransition(time_point<seconds>* tp) const { // really a sentinel, not a transition. See tz/zic.c. ++begin; } - std::int_fast64_t unix_time = ToUnixSeconds(*tp); + std::int_fast64_t unix_time = ToUnixSeconds(tp); const Transition target = { unix_time }; const Transition* tr = std::upper_bound(begin, end, target, Transition::ByUnixTime()); - if (tr != begin) { // skip no-op transitions - for (; tr != end; ++tr) { - if (!EquivTransitions(tr[-1].type_index, tr[0].type_index)) break; - } + for (; tr != end; ++tr) { // skip no-op transitions + std::uint_fast8_t prev_type_index = + (tr == begin) ? default_transition_type_ : tr[-1].type_index; + if (!EquivTransitions(prev_type_index, tr[0].type_index)) break; } // When tr == end we return false, ignoring future_spec_. if (tr == end) return false; - *tp = FromUnixSeconds(tr->unix_time); + trans->from = tr->prev_civil_sec + 1; + trans->to = tr->civil_sec; return true; } -bool TimeZoneInfo::PrevTransition(time_point<seconds>* tp) const { +bool TimeZoneInfo::PrevTransition(const time_point<seconds>& tp, + time_zone::civil_transition* trans) const { if (transitions_.empty()) return false; const Transition* begin = &transitions_[0]; const Transition* end = begin + transitions_.size(); @@ -928,11 +946,12 @@ bool TimeZoneInfo::PrevTransition(time_point<seconds>* tp) const { // really a sentinel, not a transition. See tz/zic.c. ++begin; } - std::int_fast64_t unix_time = ToUnixSeconds(*tp); - if (FromUnixSeconds(unix_time) != *tp) { + std::int_fast64_t unix_time = ToUnixSeconds(tp); + if (FromUnixSeconds(unix_time) != tp) { if (unix_time == std::numeric_limits<std::int_fast64_t>::max()) { if (end == begin) return false; // Ignore future_spec_. - *tp = FromUnixSeconds((--end)->unix_time); + trans->from = (--end)->prev_civil_sec + 1; + trans->to = end->civil_sec; return true; } unix_time += 1; // ceils @@ -940,14 +959,15 @@ bool TimeZoneInfo::PrevTransition(time_point<seconds>* tp) const { const Transition target = { unix_time }; const Transition* tr = std::lower_bound(begin, end, target, Transition::ByUnixTime()); - if (tr != begin) { // skip no-op transitions - for (; tr - 1 != begin; --tr) { - if (!EquivTransitions(tr[-2].type_index, tr[-1].type_index)) break; - } + for (; tr != begin; --tr) { // skip no-op transitions + std::uint_fast8_t prev_type_index = + (tr - 1 == begin) ? default_transition_type_ : tr[-2].type_index; + if (!EquivTransitions(prev_type_index, tr[-1].type_index)) break; } // When tr == end we return the "last" transition, ignoring future_spec_. if (tr == begin) return false; - *tp = FromUnixSeconds((--tr)->unix_time); + trans->from = (--tr)->prev_civil_sec + 1; + trans->to = tr->civil_sec; return true; } diff --git a/absl/time/internal/cctz/src/time_zone_info.h b/absl/time/internal/cctz/src/time_zone_info.h index d28443e2..958e9b6b 100644 --- a/absl/time/internal/cctz/src/time_zone_info.h +++ b/absl/time/internal/cctz/src/time_zone_info.h @@ -74,9 +74,12 @@ class TimeZoneInfo : public TimeZoneIf { const time_point<seconds>& tp) const override; time_zone::civil_lookup MakeTime( const civil_second& cs) const override; + bool NextTransition(const time_point<seconds>& tp, + time_zone::civil_transition* trans) const override; + bool PrevTransition(const time_point<seconds>& tp, + time_zone::civil_transition* trans) const override; + std::string Version() const override; std::string Description() const override; - bool NextTransition(time_point<seconds>* tp) const override; - bool PrevTransition(time_point<seconds>* tp) const override; private: struct Header { // counts of: @@ -114,6 +117,7 @@ class TimeZoneInfo : public TimeZoneIf { std::uint_fast8_t default_transition_type_; // for before first transition std::string abbreviations_; // all the NUL-terminated abbreviations + std::string version_; // the tzdata version if available std::string future_spec_; // for after the last zic transition bool extended_; // future_spec_ was used to generate transitions year_t last_year_; // the final year of the generated transitions diff --git a/absl/time/internal/cctz/src/time_zone_libc.cc b/absl/time/internal/cctz/src/time_zone_libc.cc index 1d727bde..074c8d0a 100644 --- a/absl/time/internal/cctz/src/time_zone_libc.cc +++ b/absl/time/internal/cctz/src/time_zone_libc.cc @@ -139,16 +139,22 @@ time_zone::civil_lookup TimeZoneLibC::MakeTime(const civil_second& cs) const { return cl; } -std::string TimeZoneLibC::Description() const { - return local_ ? "localtime" : "UTC"; +bool TimeZoneLibC::NextTransition(const time_point<seconds>& tp, + time_zone::civil_transition* trans) const { + return false; } -bool TimeZoneLibC::NextTransition(time_point<seconds>* tp) const { +bool TimeZoneLibC::PrevTransition(const time_point<seconds>& tp, + time_zone::civil_transition* trans) const { return false; } -bool TimeZoneLibC::PrevTransition(time_point<seconds>* tp) const { - return false; +std::string TimeZoneLibC::Version() const { + return std::string(); // unknown +} + +std::string TimeZoneLibC::Description() const { + return local_ ? "localtime" : "UTC"; } } // namespace cctz diff --git a/absl/time/internal/cctz/src/time_zone_libc.h b/absl/time/internal/cctz/src/time_zone_libc.h index 4c64cd34..4e40c61a 100644 --- a/absl/time/internal/cctz/src/time_zone_libc.h +++ b/absl/time/internal/cctz/src/time_zone_libc.h @@ -35,9 +35,12 @@ class TimeZoneLibC : public TimeZoneIf { const time_point<seconds>& tp) const override; time_zone::civil_lookup MakeTime( const civil_second& cs) const override; + bool NextTransition(const time_point<seconds>& tp, + time_zone::civil_transition* trans) const override; + bool PrevTransition(const time_point<seconds>& tp, + time_zone::civil_transition* trans) const override; + std::string Version() const override; std::string Description() const override; - bool NextTransition(time_point<seconds>* tp) const override; - bool PrevTransition(time_point<seconds>* tp) const override; private: const bool local_; // localtime or UTC diff --git a/absl/time/internal/cctz/src/time_zone_lookup.cc b/absl/time/internal/cctz/src/time_zone_lookup.cc index 2f6cd98b..f2d151e4 100644 --- a/absl/time/internal/cctz/src/time_zone_lookup.cc +++ b/absl/time/internal/cctz/src/time_zone_lookup.cc @@ -61,20 +61,43 @@ int __system_property_get(const char* name, char* value) { #endif std::string time_zone::name() const { - return time_zone::Impl::get(*this).name(); + return effective_impl().Name(); } time_zone::absolute_lookup time_zone::lookup( const time_point<seconds>& tp) const { - return time_zone::Impl::get(*this).BreakTime(tp); + return effective_impl().BreakTime(tp); } time_zone::civil_lookup time_zone::lookup(const civil_second& cs) const { - return time_zone::Impl::get(*this).MakeTime(cs); + return effective_impl().MakeTime(cs); } -bool operator==(time_zone lhs, time_zone rhs) { - return &time_zone::Impl::get(lhs) == &time_zone::Impl::get(rhs); +bool time_zone::next_transition(const time_point<seconds>& tp, + civil_transition* trans) const { + return effective_impl().NextTransition(tp, trans); +} + +bool time_zone::prev_transition(const time_point<seconds>& tp, + civil_transition* trans) const { + return effective_impl().PrevTransition(tp, trans); +} + +std::string time_zone::version() const { + return effective_impl().Version(); +} + +std::string time_zone::description() const { + return effective_impl().Description(); +} + +const time_zone::Impl& time_zone::effective_impl() const { + if (impl_ == nullptr) { + // Dereferencing an implicit-UTC time_zone is expected to be + // rare, so we don't mind paying a small synchronization cost. + return *time_zone::Impl::UTC().impl_; + } + return *impl_; } bool load_time_zone(const std::string& name, time_zone* 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 cd9fc236..551292fb 100644 --- a/absl/time/internal/cctz/src/time_zone_lookup_test.cc +++ b/absl/time/internal/cctz/src/time_zone_lookup_test.cc @@ -651,6 +651,17 @@ time_zone LoadZone(const std::string& name) { /* EXPECT_STREQ(zone, al.abbr); */ \ } while (0) +// These tests sometimes run on platforms that have zoneinfo data so old +// that the transition we are attempting to check does not exist, most +// notably Android emulators. Fortunately, AndroidZoneInfoSource supports +// time_zone::version() so, in cases where we've learned that it matters, +// we can make the check conditionally. +int VersionCmp(time_zone tz, const std::string& target) { + std::string version = tz.version(); + if (version.empty() && !target.empty()) return 1; // unknown > known + return version.compare(target); +} + } // namespace TEST(TimeZones, LoadZonesConcurrently) { @@ -981,6 +992,69 @@ TEST(MakeTime, SysSecondsLimits) { EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp); } +TEST(NextTransition, UTC) { + const auto tz = utc_time_zone(); + time_zone::civil_transition trans; + + auto tp = time_point<absl::time_internal::cctz::seconds>::min(); + EXPECT_FALSE(tz.next_transition(tp, &trans)); + + tp = time_point<absl::time_internal::cctz::seconds>::max(); + EXPECT_FALSE(tz.next_transition(tp, &trans)); +} + +TEST(PrevTransition, UTC) { + const auto tz = utc_time_zone(); + time_zone::civil_transition trans; + + auto tp = time_point<absl::time_internal::cctz::seconds>::max(); + EXPECT_FALSE(tz.prev_transition(tp, &trans)); + + tp = time_point<absl::time_internal::cctz::seconds>::min(); + EXPECT_FALSE(tz.prev_transition(tp, &trans)); +} + +TEST(NextTransition, AmericaNewYork) { + const auto tz = LoadZone("America/New_York"); + time_zone::civil_transition trans; + + auto tp = convert(civil_second(2018, 6, 30, 0, 0, 0), tz); + EXPECT_TRUE(tz.next_transition(tp, &trans)); + EXPECT_EQ(civil_second(2018, 11, 4, 2, 0, 0), trans.from); + EXPECT_EQ(civil_second(2018, 11, 4, 1, 0, 0), trans.to); + + tp = time_point<absl::time_internal::cctz::seconds>::max(); + EXPECT_FALSE(tz.next_transition(tp, &trans)); + + tp = time_point<absl::time_internal::cctz::seconds>::min(); + EXPECT_TRUE(tz.next_transition(tp, &trans)); + if (trans.from == civil_second(1918, 3, 31, 2, 0, 0)) { + // It looks like the tzdata is only 32 bit (probably macOS), + // which bottoms out at 1901-12-13T20:45:52+00:00. + EXPECT_EQ(civil_second(1918, 3, 31, 3, 0, 0), trans.to); + } else { + EXPECT_EQ(civil_second(1883, 11, 18, 12, 3, 58), trans.from); + EXPECT_EQ(civil_second(1883, 11, 18, 12, 0, 0), trans.to); + } +} + +TEST(PrevTransition, AmericaNewYork) { + const auto tz = LoadZone("America/New_York"); + time_zone::civil_transition trans; + + auto tp = convert(civil_second(2018, 6, 30, 0, 0, 0), tz); + EXPECT_TRUE(tz.prev_transition(tp, &trans)); + EXPECT_EQ(civil_second(2018, 3, 11, 2, 0, 0), trans.from); + EXPECT_EQ(civil_second(2018, 3, 11, 3, 0, 0), trans.to); + + tp = time_point<absl::time_internal::cctz::seconds>::min(); + EXPECT_FALSE(tz.prev_transition(tp, &trans)); + + tp = time_point<absl::time_internal::cctz::seconds>::max(); + EXPECT_TRUE(tz.prev_transition(tp, &trans)); + // We have a transition but we don't know which one. +} + TEST(TimeZoneEdgeCase, AmericaNewYork) { const time_zone tz = LoadZone("America/New_York"); @@ -1104,35 +1178,31 @@ TEST(TimeZoneEdgeCase, PacificApia) { TEST(TimeZoneEdgeCase, AfricaCairo) { const time_zone tz = LoadZone("Africa/Cairo"); -#if defined(__ANDROID__) && __ANDROID_API__ < 21 - // Only Android 'L' and beyond have this tz2014c transition. -#else - // An interesting case of midnight not existing. - // - // 1400191199 == Thu, 15 May 2014 23:59:59 +0200 (EET) - // 1400191200 == Fri, 16 May 2014 01:00:00 +0300 (EEST) - auto tp = convert(civil_second(2014, 5, 15, 23, 59, 59), tz); - ExpectTime(tp, tz, 2014, 5, 15, 23, 59, 59, 2 * 3600, false, "EET"); - tp += absl::time_internal::cctz::seconds(1); - ExpectTime(tp, tz, 2014, 5, 16, 1, 0, 0, 3 * 3600, true, "EEST"); -#endif + if (VersionCmp(tz, "2014c") >= 0) { + // An interesting case of midnight not existing. + // + // 1400191199 == Thu, 15 May 2014 23:59:59 +0200 (EET) + // 1400191200 == Fri, 16 May 2014 01:00:00 +0300 (EEST) + auto tp = convert(civil_second(2014, 5, 15, 23, 59, 59), tz); + ExpectTime(tp, tz, 2014, 5, 15, 23, 59, 59, 2 * 3600, false, "EET"); + tp += absl::time_internal::cctz::seconds(1); + ExpectTime(tp, tz, 2014, 5, 16, 1, 0, 0, 3 * 3600, true, "EEST"); + } } TEST(TimeZoneEdgeCase, AfricaMonrovia) { const time_zone tz = LoadZone("Africa/Monrovia"); -#if defined(__ANDROID__) && __ANDROID_API__ < 26 - // Only Android 'O' and beyond have this tz2017b transition. -#else - // Strange offset change -00:44:30 -> +00:00:00 (non-DST) - // - // 63593069 == Thu, 6 Jan 1972 23:59:59 -0044 (MMT) - // 63593070 == Fri, 7 Jan 1972 00:44:30 +0000 (GMT) - 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 += absl::time_internal::cctz::seconds(1); - ExpectTime(tp, tz, 1972, 1, 7, 0, 44, 30, 0 * 60, false, "GMT"); -#endif + if (VersionCmp(tz, "2017b") >= 0) { + // Strange offset change -00:44:30 -> +00:00:00 (non-DST) + // + // 63593069 == Thu, 6 Jan 1972 23:59:59 -0044 (MMT) + // 63593070 == Fri, 7 Jan 1972 00:44:30 +0000 (GMT) + 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 += absl::time_internal::cctz::seconds(1); + ExpectTime(tp, tz, 1972, 1, 7, 0, 44, 30, 0 * 60, false, "GMT"); + } } TEST(TimeZoneEdgeCase, AmericaJamaica) { @@ -1144,28 +1214,29 @@ TEST(TimeZoneEdgeCase, AmericaJamaica) { const time_zone tz = LoadZone("America/Jamaica"); // Before the first transition. - auto tp = convert(civil_second(1889, 12, 31, 0, 0, 0), tz); -#if AMERICA_JAMAICA_PRE_1913_OFFSET_FIX - // Commit 907241e: Fix off-by-1 error for Jamaica and T&C before 1913. - // Until that commit has made its way into a full release we avoid the - // expectations on the -18430 offset below. TODO: Uncomment these. - ExpectTime(tp, tz, 1889, 12, 31, 0, 0, 0, -18430, false, - tz.lookup(tp).abbr); - - // Over the first (abbreviation-change only) transition. - // -2524503170 == Tue, 31 Dec 1889 23:59:59 -0507 (LMT) - // -2524503169 == Wed, 1 Jan 1890 00:00:00 -0507 (KMT) - tp = convert(civil_second(1889, 12, 31, 23, 59, 59), tz); - ExpectTime(tp, tz, 1889, 12, 31, 23, 59, 59, -18430, false, - tz.lookup(tp).abbr); - tp += absl::time_internal::cctz::seconds(1); - ExpectTime(tp, tz, 1890, 1, 1, 0, 0, 0, -18430, false, "KMT"); -#endif + if (!tz.version().empty() && VersionCmp(tz, "2018d") >= 0) { + // We avoid the expectations on the -18430 offset below unless we are + // certain we have commit 907241e (Fix off-by-1 error for Jamaica and + // T&C before 1913) from 2018d. TODO: Remove the "version() not empty" + // part when 2018d is generally available from /usr/share/zoneinfo. + auto tp = convert(civil_second(1889, 12, 31, 0, 0, 0), tz); + ExpectTime(tp, tz, 1889, 12, 31, 0, 0, 0, -18430, false, + tz.lookup(tp).abbr); + + // Over the first (abbreviation-change only) transition. + // -2524503170 == Tue, 31 Dec 1889 23:59:59 -0507 (LMT) + // -2524503169 == Wed, 1 Jan 1890 00:00:00 -0507 (KMT) + tp = convert(civil_second(1889, 12, 31, 23, 59, 59), tz); + ExpectTime(tp, tz, 1889, 12, 31, 23, 59, 59, -18430, false, + tz.lookup(tp).abbr); + tp += absl::time_internal::cctz::seconds(1); + ExpectTime(tp, tz, 1890, 1, 1, 0, 0, 0, -18430, false, "KMT"); + } // Over the last (DST) transition. // 436341599 == Sun, 30 Oct 1983 01:59:59 -0400 (EDT) // 436341600 == Sun, 30 Oct 1983 01:00:00 -0500 (EST) - tp = convert(civil_second(1983, 10, 30, 1, 59, 59), tz); + auto tp = convert(civil_second(1983, 10, 30, 1, 59, 59), tz); ExpectTime(tp, tz, 1983, 10, 30, 1, 59, 59, -4 * 3600, true, "EDT"); tp += absl::time_internal::cctz::seconds(1); ExpectTime(tp, tz, 1983, 10, 30, 1, 0, 0, -5 * 3600, false, "EST"); diff --git a/absl/time/internal/cctz/src/zone_info_source.cc b/absl/time/internal/cctz/src/zone_info_source.cc index ee7500b6..bf2d2d2d 100644 --- a/absl/time/internal/cctz/src/zone_info_source.cc +++ b/absl/time/internal/cctz/src/zone_info_source.cc @@ -20,6 +20,7 @@ namespace cctz { // Defined out-of-line to avoid emitting a weak vtable in all TUs. ZoneInfoSource::~ZoneInfoSource() {} +std::string ZoneInfoSource::Version() const { return std::string(); } } // namespace cctz } // namespace time_internal diff --git a/absl/types/variant.h b/absl/types/variant.h index fd1d49ac..9d98a9ed 100644 --- a/absl/types/variant.h +++ b/absl/types/variant.h @@ -248,7 +248,7 @@ using variant_alternative_t = typename variant_alternative<I, T>::type; // // Example: // -// absl::variant<int, std::string> bar = 42; +// absl::variant<int, std::string> foo = 42; // if (absl::holds_alternative<int>(foo)) { // std::cout << "The variant holds an integer"; // } |