diff options
115 files changed, 2292 insertions, 501 deletions
diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake index d8ddcb3b..831ec5fb 100644 --- a/CMake/AbseilDll.cmake +++ b/CMake/AbseilDll.cmake @@ -238,8 +238,13 @@ set(ABSL_INTERNAL_DLL_FILES "strings/internal/cordz_statistics.h" "strings/internal/cordz_update_scope.h" "strings/internal/cordz_update_tracker.h" + "strings/internal/damerau_levenshtein_distance.h" + "strings/internal/damerau_levenshtein_distance.cc" "strings/internal/stl_type_traits.h" "strings/internal/string_constant.h" + "strings/internal/stringify_sink.h" + "strings/internal/stringify_sink.cc" + "strings/internal/has_absl_stringify.h" "strings/match.cc" "strings/match.h" "strings/numbers.cc" @@ -472,6 +477,35 @@ set(ABSL_INTERNAL_DLL_TARGETS "variant" ) +function(_absl_target_compile_features_if_available TARGET TYPE FEATURE) + if(FEATURE IN_LIST CMAKE_CXX_COMPILE_FEATURES) + target_compile_features(${TARGET} ${TYPE} ${FEATURE}) + else() + message(WARNING "Feature ${FEATURE} is unknown for the CXX compiler") + endif() +endfunction() + +include(CheckCXXSourceCompiles) + +check_cxx_source_compiles( + [==[ +#ifdef _MSC_VER +# if _MSVC_LANG < 201700L +# error "The compiler defaults or is configured for C++ < 17" +# endif +#elif __cplusplus < 201700L +# error "The compiler defaults or is configured for C++ < 17" +#endif +int main() { return 0; } +]==] + ABSL_INTERNAL_AT_LEAST_CXX17) + +if(ABSL_INTERNAL_AT_LEAST_CXX17) + set(ABSL_INTERNAL_CXX_STD_FEATURE cxx_std_17) +else() + set(ABSL_INTERNAL_CXX_STD_FEATURE cxx_std_14) +endif() + function(absl_internal_dll_contains) cmake_parse_arguments(ABSL_INTERNAL_DLL "" @@ -551,6 +585,25 @@ function(absl_make_dll) ${ABSL_CC_LIB_DEFINES} ABSL_CONSUME_DLL ) + + if(ABSL_PROPAGATE_CXX_STD) + # Abseil libraries require C++14 as the current minimum standard. When + # compiled with C++17 (either because it is the compiler's default or + # explicitly requested), then Abseil requires C++17. + _absl_target_compile_features_if_available(${_NAME} PUBLIC ${ABSL_INTERNAL_CXX_STD_FEATURE}) + else() + # Note: This is legacy (before CMake 3.8) behavior. Setting the + # target-level CXX_STANDARD property to ABSL_CXX_STANDARD (which is + # initialized by CMAKE_CXX_STANDARD) should have no real effect, since + # that is the default value anyway. + # + # CXX_STANDARD_REQUIRED does guard against the top-level CMake project + # not having enabled CMAKE_CXX_STANDARD_REQUIRED (which prevents + # "decaying" to an older standard if the requested one isn't available). + set_property(TARGET ${_NAME} PROPERTY CXX_STANDARD ${ABSL_CXX_STANDARD}) + set_property(TARGET ${_NAME} PROPERTY CXX_STANDARD_REQUIRED ON) + endif() + install(TARGETS abseil_dll EXPORT ${PROJECT_NAME}Targets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} diff --git a/CMake/AbseilHelpers.cmake b/CMake/AbseilHelpers.cmake index e1196fd7..8e08d3fc 100644 --- a/CMake/AbseilHelpers.cmake +++ b/CMake/AbseilHelpers.cmake @@ -32,35 +32,6 @@ else() set(ABSL_INTERNAL_INCLUDE_WARNING_GUARD "") endif() -function(_target_compile_features_if_available TARGET TYPE FEATURE) - if(FEATURE IN_LIST CMAKE_CXX_COMPILE_FEATURES) - target_compile_features(${TARGET} ${TYPE} ${FEATURE}) - else() - message(WARNING "Feature ${FEATURE} is unknown for the CXX compiler") - endif() -endfunction() - -include(CheckCXXSourceCompiles) - -check_cxx_source_compiles( - [==[ -#ifdef _MSC_VER -# if _MSVC_LANG < 201700L -# error "The compiler defaults or is configured for C++ < 17" -# endif -#elif __cplusplus < 201700L -# error "The compiler defaults or is configured for C++ < 17" -#endif -int main() { return 0; } -]==] - ABSL_INTERNAL_AT_LEAST_CXX17) - -if(ABSL_INTERNAL_AT_LEAST_CXX17) - set(ABSL_INTERNAL_CXX_STD_FEATURE cxx_std_17) -else() - set(ABSL_INTERNAL_CXX_STD_FEATURE cxx_std_14) -endif() - # absl_cc_library() # # CMake function to imitate Bazel's cc_library rule. @@ -297,10 +268,10 @@ Cflags: -I\${includedir}${PC_CFLAGS}\n") endif() if(ABSL_PROPAGATE_CXX_STD) - # Abseil libraries require C++14 as the current minimum standard. - # Top-level application CMake projects should ensure a consistent C++ - # standard for all compiled sources by setting CMAKE_CXX_STANDARD. - _target_compile_features_if_available(${_NAME} PUBLIC ${ABSL_INTERNAL_CXX_STD_FEATURE}) + # Abseil libraries require C++14 as the current minimum standard. When + # compiled with C++17 (either because it is the compiler's default or + # explicitly requested), then Abseil requires C++17. + _absl_target_compile_features_if_available(${_NAME} PUBLIC ${ABSL_INTERNAL_CXX_STD_FEATURE}) else() # Note: This is legacy (before CMake 3.8) behavior. Setting the # target-level CXX_STANDARD property to ABSL_CXX_STANDARD (which is @@ -348,7 +319,7 @@ Cflags: -I\${includedir}${PC_CFLAGS}\n") # Abseil libraries require C++14 as the current minimum standard. # Top-level application CMake projects should ensure a consistent C++ # standard for all compiled sources by setting CMAKE_CXX_STANDARD. - _target_compile_features_if_available(${_NAME} INTERFACE ${ABSL_INTERNAL_CXX_STD_FEATURE}) + _absl_target_compile_features_if_available(${_NAME} INTERFACE ${ABSL_INTERNAL_CXX_STD_FEATURE}) # (INTERFACE libraries can't have the CXX_STANDARD property set, so there # is no legacy behavior else case). @@ -460,7 +431,7 @@ function(absl_cc_test) # Abseil libraries require C++14 as the current minimum standard. # Top-level application CMake projects should ensure a consistent C++ # standard for all compiled sources by setting CMAKE_CXX_STANDARD. - _target_compile_features_if_available(${_NAME} PUBLIC ${ABSL_INTERNAL_CXX_STD_FEATURE}) + _absl_target_compile_features_if_available(${_NAME} PUBLIC ${ABSL_INTERNAL_CXX_STD_FEATURE}) else() # Note: This is legacy (before CMake 3.8) behavior. Setting the # target-level CXX_STANDARD property to ABSL_CXX_STANDARD (which is @@ -476,11 +447,3 @@ function(absl_cc_test) add_test(NAME ${_NAME} COMMAND ${_NAME}) endfunction() - - -function(check_target my_target) - if(NOT TARGET ${my_target}) - message(FATAL_ERROR " ABSL: compiling absl requires a ${my_target} CMake target in your project, - see CMake/README.md for more details") - endif(NOT TARGET ${my_target}) -endfunction() diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b67d8fe..9e102578 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -140,7 +140,6 @@ set(ABSL_LOCAL_GOOGLETEST_DIR "/usr/src/googletest" CACHE PATH ) if((BUILD_TESTING AND ABSL_BUILD_TESTING) OR ABSL_BUILD_TEST_HELPERS) - ## check targets if (ABSL_USE_EXTERNAL_GOOGLETEST) if (ABSL_FIND_GOOGLETEST) find_package(GTest REQUIRED) @@ -172,11 +171,6 @@ if((BUILD_TESTING AND ABSL_BUILD_TESTING) OR ABSL_BUILD_TEST_HELPERS) endif() include(CMake/Googletest/DownloadGTest.cmake) endif() - - check_target(GTest::gtest) - check_target(GTest::gtest_main) - check_target(GTest::gmock) - check_target(GTest::gmock_main) endif() add_subdirectory(absl) diff --git a/absl/base/CMakeLists.txt b/absl/base/CMakeLists.txt index 5c46ba32..26e2b48a 100644 --- a/absl/base/CMakeLists.txt +++ b/absl/base/CMakeLists.txt @@ -201,7 +201,7 @@ absl_cc_library( LINKOPTS ${ABSL_DEFAULT_LINKOPTS} $<$<BOOL:${LIBRT}>:-lrt> - $<$<BOOL:${MINGW}>:"advapi32"> + $<$<BOOL:${MINGW}>:-ladvapi32> DEPS absl::atomic_hook absl::base_internal diff --git a/absl/base/config.h b/absl/base/config.h index 95131068..1058ce74 100644 --- a/absl/base/config.h +++ b/absl/base/config.h @@ -243,6 +243,7 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' || #ifdef ABSL_HAVE_STD_IS_TRIVIALLY_DESTRUCTIBLE #error ABSL_HAVE_STD_IS_TRIVIALLY_DESTRUCTIBLE cannot be directly set #elif defined(_LIBCPP_VERSION) || defined(_MSC_VER) || \ + (defined(__clang__) && __clang_major__ >= 15) || \ (!defined(__clang__) && defined(__GLIBCXX__) && \ ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(4, 8)) #define ABSL_HAVE_STD_IS_TRIVIALLY_DESTRUCTIBLE 1 @@ -264,6 +265,7 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' || #elif defined(ABSL_HAVE_STD_IS_TRIVIALLY_ASSIGNABLE) #error ABSL_HAVE_STD_IS_TRIVIALLY_ASSIGNABLE cannot directly set #elif (defined(__clang__) && defined(_LIBCPP_VERSION)) || \ + (defined(__clang__) && __clang_major__ >= 15) || \ (!defined(__clang__) && \ ((ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(7, 4) && defined(__GLIBCXX__)) || \ (ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(8, 2) && \ diff --git a/absl/base/optimization.h b/absl/base/optimization.h index db5cc097..d706100c 100644 --- a/absl/base/optimization.h +++ b/absl/base/optimization.h @@ -91,6 +91,7 @@ #define ABSL_CACHELINE_SIZE 64 #endif #endif +#endif #ifndef ABSL_CACHELINE_SIZE // A reasonable default guess. Note that overestimates tend to waste more @@ -141,12 +142,11 @@ // the generated machine code. // 3) Prefer applying this attribute to individual variables. Avoid // applying it to types. This tends to localize the effect. +#if defined(__clang__) || defined(__GNUC__) #define ABSL_CACHELINE_ALIGNED __attribute__((aligned(ABSL_CACHELINE_SIZE))) #elif defined(_MSC_VER) -#define ABSL_CACHELINE_SIZE 64 #define ABSL_CACHELINE_ALIGNED __declspec(align(ABSL_CACHELINE_SIZE)) #else -#define ABSL_CACHELINE_SIZE 64 #define ABSL_CACHELINE_ALIGNED #endif diff --git a/absl/base/policy_checks.h b/absl/base/policy_checks.h index d13073c8..2626fb6a 100644 --- a/absl/base/policy_checks.h +++ b/absl/base/policy_checks.h @@ -44,10 +44,10 @@ // Toolchain Check // ----------------------------------------------------------------------------- -// We support MSVC++ 14.0 update 2 and later. +// We support Visual Studio 2017 (MSVC++ 15.0) and later. // This minimum will go up. -#if defined(_MSC_FULL_VER) && _MSC_FULL_VER < 190023918 && !defined(__clang__) -#error "This package requires Visual Studio 2015 Update 2 or higher." +#if defined(_MSC_VER) && _MSC_VER < 1910 && !defined(__clang__) +#error "This package requires Visual Studio 2017 (MSVC++ 15.0) or higher." #endif // We support gcc 5 and later. diff --git a/absl/container/BUILD.bazel b/absl/container/BUILD.bazel index 71afe9d2..70febdda 100644 --- a/absl/container/BUILD.bazel +++ b/absl/container/BUILD.bazel @@ -134,6 +134,7 @@ cc_library( "//absl/base:core_headers", "//absl/base:throw_delegate", "//absl/memory", + "//absl/meta:type_traits", ], ) @@ -637,6 +638,8 @@ cc_test( ], deps = [ ":container_memory", + ":flat_hash_map", + ":flat_hash_set", ":hash_function_defaults", ":hash_policy_testing", ":hashtable_debug", @@ -646,6 +649,7 @@ cc_test( "//absl/base:core_headers", "//absl/base:prefetch", "//absl/base:raw_logging_internal", + "//absl/log", "//absl/strings", "@com_google_googletest//:gtest_main", ], @@ -980,11 +984,13 @@ cc_test( ":btree_test_common", ":counting_allocator", ":test_instance_tracker", + "//absl/algorithm:container", "//absl/base:core_headers", "//absl/base:raw_logging_internal", "//absl/flags:flag", "//absl/hash:hash_testing", "//absl/memory", + "//absl/random", "//absl/strings", "//absl/types:compare", "@com_google_googletest//:gtest_main", @@ -1007,10 +1013,12 @@ cc_binary( ":flat_hash_map", ":flat_hash_set", ":hashtable_debug", + "//absl/algorithm:container", "//absl/base:raw_logging_internal", - "//absl/flags:flag", "//absl/hash", + "//absl/log", "//absl/memory", + "//absl/random", "//absl/strings:cord", "//absl/strings:str_format", "//absl/time", diff --git a/absl/container/CMakeLists.txt b/absl/container/CMakeLists.txt index a3fdb969..b3776aed 100644 --- a/absl/container/CMakeLists.txt +++ b/absl/container/CMakeLists.txt @@ -72,6 +72,7 @@ absl_cc_test( LINKOPTS ${ABSL_DEFAULT_LINKOPTS} DEPS + absl::algorithm_container absl::btree absl::btree_test_common absl::compare @@ -79,6 +80,7 @@ absl_cc_test( absl::counting_allocator absl::flags absl::hash_testing + absl::random_random absl::raw_logging_internal absl::strings absl::test_instance_tracker @@ -194,6 +196,7 @@ absl_cc_library( absl::inlined_vector_internal absl::throw_delegate absl::memory + absl::type_traits PUBLIC ) @@ -722,12 +725,15 @@ absl_cc_test( ${ABSL_TEST_COPTS} DEPS absl::container_memory + absl::flat_hash_map + absl::flat_hash_set absl::hash_function_defaults absl::hash_policy_testing absl::hashtable_debug absl::raw_hash_set absl::base absl::config + absl::log absl::core_headers absl::prefetch absl::raw_logging_internal diff --git a/absl/container/btree_benchmark.cc b/absl/container/btree_benchmark.cc index 0ca497c8..0d26fd42 100644 --- a/absl/container/btree_benchmark.cc +++ b/absl/container/btree_benchmark.cc @@ -27,6 +27,7 @@ #include <vector> #include "benchmark/benchmark.h" +#include "absl/algorithm/container.h" #include "absl/base/internal/raw_logging.h" #include "absl/container/btree_map.h" #include "absl/container/btree_set.h" @@ -34,9 +35,10 @@ #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" #include "absl/container/internal/hashtable_debug.h" -#include "absl/flags/flag.h" #include "absl/hash/hash.h" +#include "absl/log/log.h" #include "absl/memory/memory.h" +#include "absl/random/random.h" #include "absl/strings/cord.h" #include "absl/strings/str_format.h" #include "absl/time/time.h" @@ -733,6 +735,29 @@ double ContainerInfo(const btree_map<int, BigTypePtr<Size>>& b) { BIG_TYPE_PTR_BENCHMARKS(32); +void BM_BtreeSet_IteratorSubtraction(benchmark::State& state) { + absl::InsecureBitGen bitgen; + std::vector<int> vec; + // Randomize the set's insertion order so the nodes aren't all full. + vec.reserve(state.range(0)); + for (int i = 0; i < state.range(0); ++i) vec.push_back(i); + absl::c_shuffle(vec, bitgen); + + absl::btree_set<int> set; + for (int i : vec) set.insert(i); + + size_t distance = absl::Uniform(bitgen, 0u, set.size()); + while (state.KeepRunningBatch(distance)) { + size_t end = absl::Uniform(bitgen, distance, set.size()); + size_t begin = end - distance; + benchmark::DoNotOptimize(set.find(static_cast<int>(end)) - + set.find(static_cast<int>(begin))); + distance = absl::Uniform(bitgen, 0u, set.size()); + } +} + +BENCHMARK(BM_BtreeSet_IteratorSubtraction)->Range(1 << 10, 1 << 20); + } // namespace } // namespace container_internal ABSL_NAMESPACE_END diff --git a/absl/container/btree_map.h b/absl/container/btree_map.h index 286817f1..819a925f 100644 --- a/absl/container/btree_map.h +++ b/absl/container/btree_map.h @@ -44,8 +44,11 @@ // an issue if insertion and deletion operations are interleaved with the use of // more than one iterator, pointer, or reference simultaneously. For this // reason, `insert()` and `erase()` return a valid iterator at the current -// position. Another important difference is that key-types must be -// copy-constructible. +// position (and `extract()` cannot be used in this way). Another important +// difference is that key-types must be copy-constructible. +// +// Another API difference is that btree iterators can be subtracted, and this +// is faster than using std::distance. #ifndef ABSL_CONTAINER_BTREE_MAP_H_ #define ABSL_CONTAINER_BTREE_MAP_H_ @@ -322,7 +325,8 @@ class btree_map // btree_map::extract() // // Extracts the indicated element, erasing it in the process, and returns it - // as a C++17-compatible node handle. Overloads are listed below. + // as a C++17-compatible node handle. Any references, pointers, or iterators + // are invalidated. Overloads are listed below. // // node_type extract(const_iterator position): // diff --git a/absl/container/btree_set.h b/absl/container/btree_set.h index e823a2a0..d93bdbf6 100644 --- a/absl/container/btree_set.h +++ b/absl/container/btree_set.h @@ -44,7 +44,10 @@ // an issue if insertion and deletion operations are interleaved with the use of // more than one iterator, pointer, or reference simultaneously. For this // reason, `insert()` and `erase()` return a valid iterator at the current -// position. +// position (and `extract()` cannot be used in this way). +// +// Another API difference is that btree iterators can be subtracted, and this +// is faster than using std::distance. #ifndef ABSL_CONTAINER_BTREE_SET_H_ #define ABSL_CONTAINER_BTREE_SET_H_ @@ -269,7 +272,8 @@ class btree_set // btree_set::extract() // // Extracts the indicated element, erasing it in the process, and returns it - // as a C++17-compatible node handle. Overloads are listed below. + // as a C++17-compatible node handle. Any references, pointers, or iterators + // are invalidated. Overloads are listed below. // // node_type extract(const_iterator position): // diff --git a/absl/container/btree_test.cc b/absl/container/btree_test.cc index 9386a6b1..e6d4e360 100644 --- a/absl/container/btree_test.cc +++ b/absl/container/btree_test.cc @@ -31,6 +31,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/algorithm/container.h" #include "absl/base/internal/raw_logging.h" #include "absl/base/macros.h" #include "absl/container/btree_map.h" @@ -40,6 +41,7 @@ #include "absl/flags/flag.h" #include "absl/hash/hash_testing.h" #include "absl/memory/memory.h" +#include "absl/random/random.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_split.h" #include "absl/strings/string_view.h" @@ -3320,6 +3322,32 @@ TEST(Btree, ReusePoisonMemory) { set.insert(0); } +TEST(Btree, IteratorSubtraction) { + absl::BitGen bitgen; + std::vector<int> vec; + // Randomize the set's insertion order so the nodes aren't all full. + for (int i = 0; i < 1000000; ++i) vec.push_back(i); + absl::c_shuffle(vec, bitgen); + + absl::btree_set<int> set; + for (int i : vec) set.insert(i); + + for (int i = 0; i < 1000; ++i) { + size_t begin = absl::Uniform(bitgen, 0u, set.size()); + size_t end = absl::Uniform(bitgen, begin, set.size()); + ASSERT_EQ(end - begin, set.find(end) - set.find(begin)) + << begin << " " << end; + } +} + +#ifndef NDEBUG +TEST(Btree, DereferencingEndIterator) { + absl::btree_set<int> set; + for (int i = 0; i < 1000; ++i) set.insert(i); + EXPECT_DEATH(*set.end(), R"regex(Dereferencing end\(\) iterator)regex"); +} +#endif + } // namespace } // namespace container_internal ABSL_NAMESPACE_END diff --git a/absl/container/inlined_vector.h b/absl/container/inlined_vector.h index 60f12460..15616001 100644 --- a/absl/container/inlined_vector.h +++ b/absl/container/inlined_vector.h @@ -52,6 +52,7 @@ #include "absl/base/port.h" #include "absl/container/internal/inlined_vector.h" #include "absl/memory/memory.h" +#include "absl/meta/type_traits.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -77,6 +78,8 @@ class InlinedVector { using MoveIterator = inlined_vector_internal::MoveIterator<TheA>; template <typename TheA> using IsMemcpyOk = inlined_vector_internal::IsMemcpyOk<TheA>; + template <typename TheA> + using IsMoveAssignOk = inlined_vector_internal::IsMoveAssignOk<TheA>; template <typename TheA, typename Iterator> using IteratorValueAdapter = @@ -94,6 +97,12 @@ class InlinedVector { using DisableIfAtLeastForwardIterator = absl::enable_if_t< !inlined_vector_internal::IsAtLeastForwardIterator<Iterator>::value, int>; + using MemcpyPolicy = typename Storage::MemcpyPolicy; + using ElementwiseAssignPolicy = typename Storage::ElementwiseAssignPolicy; + using ElementwiseConstructPolicy = + typename Storage::ElementwiseConstructPolicy; + using MoveAssignmentPolicy = typename Storage::MoveAssignmentPolicy; + public: using allocator_type = A; using value_type = inlined_vector_internal::ValueType<A>; @@ -486,18 +495,7 @@ class InlinedVector { // unspecified state. InlinedVector& operator=(InlinedVector&& other) { if (ABSL_PREDICT_TRUE(this != std::addressof(other))) { - if (IsMemcpyOk<A>::value || other.storage_.GetIsAllocated()) { - inlined_vector_internal::DestroyAdapter<A>::DestroyElements( - storage_.GetAllocator(), data(), size()); - storage_.DeallocateIfAllocated(); - storage_.MemcpyFrom(other.storage_); - - other.storage_.SetInlinedSize(0); - } else { - storage_.Assign(IteratorValueAdapter<A, MoveIterator<A>>( - MoveIterator<A>(other.storage_.GetInlinedData())), - other.size()); - } + MoveAssignment(MoveAssignmentPolicy{}, std::move(other)); } return *this; @@ -773,6 +771,42 @@ class InlinedVector { template <typename H, typename TheT, size_t TheN, typename TheA> friend H AbslHashValue(H h, const absl::InlinedVector<TheT, TheN, TheA>& a); + void MoveAssignment(MemcpyPolicy, InlinedVector&& other) { + inlined_vector_internal::DestroyAdapter<A>::DestroyElements( + storage_.GetAllocator(), data(), size()); + storage_.DeallocateIfAllocated(); + storage_.MemcpyFrom(other.storage_); + + other.storage_.SetInlinedSize(0); + } + + void MoveAssignment(ElementwiseAssignPolicy, InlinedVector&& other) { + if (other.storage_.GetIsAllocated()) { + MoveAssignment(MemcpyPolicy{}, std::move(other)); + } else { + storage_.Assign(IteratorValueAdapter<A, MoveIterator<A>>( + MoveIterator<A>(other.storage_.GetInlinedData())), + other.size()); + } + } + + void MoveAssignment(ElementwiseConstructPolicy, InlinedVector&& other) { + if (other.storage_.GetIsAllocated()) { + MoveAssignment(MemcpyPolicy{}, std::move(other)); + } else { + inlined_vector_internal::DestroyAdapter<A>::DestroyElements( + storage_.GetAllocator(), data(), size()); + storage_.DeallocateIfAllocated(); + + IteratorValueAdapter<A, MoveIterator<A>> other_values( + MoveIterator<A>(other.storage_.GetInlinedData())); + inlined_vector_internal::ConstructElements<A>( + storage_.GetAllocator(), storage_.GetInlinedData(), other_values, + other.storage_.GetSize()); + storage_.SetInlinedSize(other.storage_.GetSize()); + } + } + Storage storage_; }; diff --git a/absl/container/inlined_vector_test.cc b/absl/container/inlined_vector_test.cc index b872eb45..1dc6c81b 100644 --- a/absl/container/inlined_vector_test.cc +++ b/absl/container/inlined_vector_test.cc @@ -16,12 +16,14 @@ #include <algorithm> #include <forward_list> +#include <iterator> #include <list> #include <memory> #include <scoped_allocator> #include <sstream> #include <stdexcept> #include <string> +#include <utility> #include <vector> #include "gmock/gmock.h" @@ -49,6 +51,7 @@ using testing::ElementsAre; using testing::ElementsAreArray; using testing::Eq; using testing::Gt; +using testing::Pointwise; using testing::PrintToString; using IntVec = absl::InlinedVector<int, 8>; @@ -1824,4 +1827,199 @@ TEST(InlinedVectorTest, AbslHashValueWorks) { EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(cases)); } +class MoveConstructibleOnlyInstance + : public absl::test_internal::BaseCountedInstance { + public: + explicit MoveConstructibleOnlyInstance(int x) : BaseCountedInstance(x) {} + MoveConstructibleOnlyInstance(MoveConstructibleOnlyInstance&& other) = + default; + MoveConstructibleOnlyInstance& operator=( + MoveConstructibleOnlyInstance&& other) = delete; +}; + +MATCHER(HasValue, "") { + return ::testing::get<0>(arg).value() == ::testing::get<1>(arg); +} + +TEST(NonAssignableMoveAssignmentTest, AllocatedToInline) { + using X = MoveConstructibleOnlyInstance; + InstanceTracker tracker; + absl::InlinedVector<X, 2> inlined; + inlined.emplace_back(1); + absl::InlinedVector<X, 2> allocated; + allocated.emplace_back(1); + allocated.emplace_back(2); + allocated.emplace_back(3); + tracker.ResetCopiesMovesSwaps(); + + inlined = std::move(allocated); + // passed ownership of the allocated storage + EXPECT_EQ(tracker.moves(), 0); + EXPECT_EQ(tracker.live_instances(), 3); + + EXPECT_THAT(inlined, Pointwise(HasValue(), {1, 2, 3})); +} + +TEST(NonAssignableMoveAssignmentTest, InlineToAllocated) { + using X = MoveConstructibleOnlyInstance; + InstanceTracker tracker; + absl::InlinedVector<X, 2> inlined; + inlined.emplace_back(1); + absl::InlinedVector<X, 2> allocated; + allocated.emplace_back(1); + allocated.emplace_back(2); + allocated.emplace_back(3); + tracker.ResetCopiesMovesSwaps(); + + allocated = std::move(inlined); + // Moved elements + EXPECT_EQ(tracker.moves(), 1); + EXPECT_EQ(tracker.live_instances(), 1); + + EXPECT_THAT(allocated, Pointwise(HasValue(), {1})); +} + +TEST(NonAssignableMoveAssignmentTest, InlineToInline) { + using X = MoveConstructibleOnlyInstance; + InstanceTracker tracker; + absl::InlinedVector<X, 2> inlined_a; + inlined_a.emplace_back(1); + absl::InlinedVector<X, 2> inlined_b; + inlined_b.emplace_back(1); + tracker.ResetCopiesMovesSwaps(); + + inlined_a = std::move(inlined_b); + // Moved elements + EXPECT_EQ(tracker.moves(), 1); + EXPECT_EQ(tracker.live_instances(), 1); + + EXPECT_THAT(inlined_a, Pointwise(HasValue(), {1})); +} + +TEST(NonAssignableMoveAssignmentTest, AllocatedToAllocated) { + using X = MoveConstructibleOnlyInstance; + InstanceTracker tracker; + absl::InlinedVector<X, 2> allocated_a; + allocated_a.emplace_back(1); + allocated_a.emplace_back(2); + allocated_a.emplace_back(3); + absl::InlinedVector<X, 2> allocated_b; + allocated_b.emplace_back(4); + allocated_b.emplace_back(5); + allocated_b.emplace_back(6); + allocated_b.emplace_back(7); + tracker.ResetCopiesMovesSwaps(); + + allocated_a = std::move(allocated_b); + // passed ownership of the allocated storage + EXPECT_EQ(tracker.moves(), 0); + EXPECT_EQ(tracker.live_instances(), 4); + + EXPECT_THAT(allocated_a, Pointwise(HasValue(), {4, 5, 6, 7})); +} + +TEST(NonAssignableMoveAssignmentTest, AssignThis) { + using X = MoveConstructibleOnlyInstance; + InstanceTracker tracker; + absl::InlinedVector<X, 2> v; + v.emplace_back(1); + v.emplace_back(2); + v.emplace_back(3); + + tracker.ResetCopiesMovesSwaps(); + + // Obfuscated in order to pass -Wself-move. + v = std::move(*std::addressof(v)); + // nothing happens + EXPECT_EQ(tracker.moves(), 0); + EXPECT_EQ(tracker.live_instances(), 3); + + EXPECT_THAT(v, Pointwise(HasValue(), {1, 2, 3})); +} + +class NonSwappableInstance : public absl::test_internal::BaseCountedInstance { + public: + explicit NonSwappableInstance(int x) : BaseCountedInstance(x) {} + NonSwappableInstance(const NonSwappableInstance& other) = default; + NonSwappableInstance& operator=(const NonSwappableInstance& other) = default; + NonSwappableInstance(NonSwappableInstance&& other) = default; + NonSwappableInstance& operator=(NonSwappableInstance&& other) = default; +}; + +void swap(NonSwappableInstance&, NonSwappableInstance&) = delete; + +TEST(NonSwappableSwapTest, InlineAndAllocatedTransferStorageAndMove) { + using X = NonSwappableInstance; + InstanceTracker tracker; + absl::InlinedVector<X, 2> inlined; + inlined.emplace_back(1); + absl::InlinedVector<X, 2> allocated; + allocated.emplace_back(1); + allocated.emplace_back(2); + allocated.emplace_back(3); + tracker.ResetCopiesMovesSwaps(); + + inlined.swap(allocated); + EXPECT_EQ(tracker.moves(), 1); + EXPECT_EQ(tracker.live_instances(), 4); + + EXPECT_THAT(inlined, Pointwise(HasValue(), {1, 2, 3})); +} + +TEST(NonSwappableSwapTest, InlineAndInlineMoveIndividualElements) { + using X = NonSwappableInstance; + InstanceTracker tracker; + absl::InlinedVector<X, 2> inlined_a; + inlined_a.emplace_back(1); + absl::InlinedVector<X, 2> inlined_b; + inlined_b.emplace_back(2); + tracker.ResetCopiesMovesSwaps(); + + inlined_a.swap(inlined_b); + EXPECT_EQ(tracker.moves(), 3); + EXPECT_EQ(tracker.live_instances(), 2); + + EXPECT_THAT(inlined_a, Pointwise(HasValue(), {2})); + EXPECT_THAT(inlined_b, Pointwise(HasValue(), {1})); +} + +TEST(NonSwappableSwapTest, AllocatedAndAllocatedOnlyTransferStorage) { + using X = NonSwappableInstance; + InstanceTracker tracker; + absl::InlinedVector<X, 2> allocated_a; + allocated_a.emplace_back(1); + allocated_a.emplace_back(2); + allocated_a.emplace_back(3); + absl::InlinedVector<X, 2> allocated_b; + allocated_b.emplace_back(4); + allocated_b.emplace_back(5); + allocated_b.emplace_back(6); + allocated_b.emplace_back(7); + tracker.ResetCopiesMovesSwaps(); + + allocated_a.swap(allocated_b); + EXPECT_EQ(tracker.moves(), 0); + EXPECT_EQ(tracker.live_instances(), 7); + + EXPECT_THAT(allocated_a, Pointwise(HasValue(), {4, 5, 6, 7})); + EXPECT_THAT(allocated_b, Pointwise(HasValue(), {1, 2, 3})); +} + +TEST(NonSwappableSwapTest, SwapThis) { + using X = NonSwappableInstance; + InstanceTracker tracker; + absl::InlinedVector<X, 2> v; + v.emplace_back(1); + v.emplace_back(2); + v.emplace_back(3); + + tracker.ResetCopiesMovesSwaps(); + + v.swap(v); + EXPECT_EQ(tracker.moves(), 0); + EXPECT_EQ(tracker.live_instances(), 3); + + EXPECT_THAT(v, Pointwise(HasValue(), {1, 2, 3})); +} + } // anonymous namespace diff --git a/absl/container/internal/btree.h b/absl/container/internal/btree.h index ecf31bea..2e21dc66 100644 --- a/absl/container/internal/btree.h +++ b/absl/container/internal/btree.h @@ -1085,12 +1085,25 @@ class btree_iterator { return node_ != other.node_ || position_ != other.position_; } + // Returns n such that n calls to ++other yields *this. + // Precondition: n exists. + difference_type operator-(const_iterator other) const { + if (node_ == other.node_) { + if (node_->is_leaf()) return position_ - other.position_; + if (position_ == other.position_) return 0; + } + return distance_slow(other); + } + // Accessors for the key/value the iterator is pointing at. reference operator*() const { ABSL_HARDENING_ASSERT(node_ != nullptr); - ABSL_HARDENING_ASSERT(node_->start() <= position_); - ABSL_HARDENING_ASSERT(node_->finish() > position_); assert_valid_generation(); + ABSL_HARDENING_ASSERT(position_ >= node_->start()); + if (position_ >= node_->finish()) { + ABSL_HARDENING_ASSERT(!IsEndIterator() && "Dereferencing end() iterator"); + ABSL_HARDENING_ASSERT(position_ < node_->finish()); + } return node_->value(static_cast<field_type>(position_)); } pointer operator->() const { return &operator*(); } @@ -1148,6 +1161,20 @@ class btree_iterator { #endif } + bool IsEndIterator() const { + if (position_ != node_->finish()) return false; + // Navigate to the rightmost node. + node_type *node = node_; + while (!node->is_root()) node = node->parent(); + while (node->is_internal()) node = node->child(node->finish()); + return node == node_; + } + + // Returns n such that n calls to ++other yields *this. + // Precondition: n exists && (this->node_ != other.node_ || + // !this->node_->is_leaf() || this->position_ != other.position_). + difference_type distance_slow(const_iterator other) const; + // Increment/decrement the iterator. void increment() { assert_valid_generation(); @@ -1975,6 +2002,64 @@ void btree_node<P>::clear_and_delete(btree_node *node, allocator_type *alloc) { //// // btree_iterator methods + +// Note: the implementation here is based on btree_node::clear_and_delete. +template <typename N, typename R, typename P> +auto btree_iterator<N, R, P>::distance_slow(const_iterator other) const + -> difference_type { + const_iterator begin = other; + const_iterator end = *this; + assert(begin.node_ != end.node_ || !begin.node_->is_leaf() || + begin.position_ != end.position_); + + const node_type *node = begin.node_; + // We need to compensate for double counting if begin.node_ is a leaf node. + difference_type count = node->is_leaf() ? -begin.position_ : 0; + + // First navigate to the leftmost leaf node past begin. + if (node->is_internal()) { + ++count; + node = node->child(begin.position_ + 1); + } + while (node->is_internal()) node = node->start_child(); + + // Use `size_type` because `pos` needs to be able to hold `kNodeSlots+1`, + // which isn't guaranteed to be a valid `field_type`. + size_type pos = node->position(); + const node_type *parent = node->parent(); + for (;;) { + // In each iteration of the next loop, we count one leaf node and go right. + assert(pos <= parent->finish()); + do { + node = parent->child(static_cast<field_type>(pos)); + if (node->is_internal()) { + // Navigate to the leftmost leaf under node. + while (node->is_internal()) node = node->start_child(); + pos = node->position(); + parent = node->parent(); + } + if (node == end.node_) return count + end.position_; + if (parent == end.node_ && pos == static_cast<size_type>(end.position_)) + return count + node->count(); + // +1 is for the next internal node value. + count += node->count() + 1; + ++pos; + } while (pos <= parent->finish()); + + // Once we've counted all children of parent, go up/right. + assert(pos > parent->finish()); + do { + node = parent; + pos = node->position(); + parent = node->parent(); + // -1 because we counted the value at end and shouldn't. + if (parent == end.node_ && pos == static_cast<size_type>(end.position_)) + return count - 1; + ++pos; + } while (pos > parent->finish()); + } +} + template <typename N, typename R, typename P> void btree_iterator<N, R, P>::increment_slow() { if (node_->is_leaf()) { @@ -2371,7 +2456,7 @@ auto btree<P>::rebalance_after_delete(iterator iter) -> iterator { template <typename P> auto btree<P>::erase_range(iterator begin, iterator end) -> std::pair<size_type, iterator> { - size_type count = static_cast<size_type>(std::distance(begin, end)); + size_type count = static_cast<size_type>(end - begin); assert(count >= 0); if (count == 0) { diff --git a/absl/container/internal/btree_container.h b/absl/container/internal/btree_container.h index fc2f740a..3e259861 100644 --- a/absl/container/internal/btree_container.h +++ b/absl/container/internal/btree_container.h @@ -107,7 +107,7 @@ class btree_container { template <typename K = key_type> size_type count(const key_arg<K> &key) const { auto equal_range = this->equal_range(key); - return std::distance(equal_range.first, equal_range.second); + return equal_range.second - equal_range.first; } template <typename K = key_type> iterator find(const key_arg<K> &key) { diff --git a/absl/container/internal/common_policy_traits.h b/absl/container/internal/common_policy_traits.h index c99e68f4..0fd4866e 100644 --- a/absl/container/internal/common_policy_traits.h +++ b/absl/container/internal/common_policy_traits.h @@ -93,11 +93,12 @@ struct common_policy_traits { slot_type* old_slot, char) { #if defined(__cpp_lib_launder) && __cpp_lib_launder >= 201606 if (absl::is_trivially_relocatable<value_type>()) { - // TODO(b/247130232): remove cast after fixing class-memaccess warning. + // TODO(b/247130232,b/251814870): remove casts after fixing warnings. std::memcpy(static_cast<void*>( std::launder(const_cast<std::remove_const_t<value_type>*>( &element(new_slot)))), - &element(old_slot), sizeof(value_type)); + static_cast<const void*>(&element(old_slot)), + sizeof(value_type)); return; } #endif diff --git a/absl/container/internal/container_memory.h b/absl/container/internal/container_memory.h index c29c533b..bfa4ff93 100644 --- a/absl/container/internal/container_memory.h +++ b/absl/container/internal/container_memory.h @@ -428,9 +428,10 @@ struct map_slot_policy { emplace(new_slot); #if defined(__cpp_lib_launder) && __cpp_lib_launder >= 201606 if (absl::is_trivially_relocatable<value_type>()) { - // TODO(b/247130232): remove cast after fixing class-memaccess warning. + // TODO(b/247130232,b/251814870): remove casts after fixing warnings. std::memcpy(static_cast<void*>(std::launder(&new_slot->value)), - &old_slot->value, sizeof(value_type)); + static_cast<const void*>(&old_slot->value), + sizeof(value_type)); return; } #endif diff --git a/absl/container/internal/inlined_vector.h b/absl/container/internal/inlined_vector.h index 7ae3da84..48f31c2e 100644 --- a/absl/container/internal/inlined_vector.h +++ b/absl/container/internal/inlined_vector.h @@ -83,6 +83,11 @@ using IsMemcpyOk = absl::is_trivially_copy_assignable<ValueType<A>>, absl::is_trivially_destructible<ValueType<A>>>; +template <typename A> +using IsMoveAssignOk = std::is_move_assignable<ValueType<A>>; +template <typename A> +using IsSwapOk = absl::type_traits_internal::IsSwappable<ValueType<A>>; + template <typename T> struct TypeIdentity { using type = T; @@ -297,6 +302,20 @@ class ConstructionTransaction { template <typename T, size_t N, typename A> class Storage { public: + struct MemcpyPolicy {}; + struct ElementwiseAssignPolicy {}; + struct ElementwiseSwapPolicy {}; + struct ElementwiseConstructPolicy {}; + + using MoveAssignmentPolicy = absl::conditional_t< + IsMemcpyOk<A>::value, MemcpyPolicy, + absl::conditional_t<IsMoveAssignOk<A>::value, ElementwiseAssignPolicy, + ElementwiseConstructPolicy>>; + using SwapPolicy = absl::conditional_t< + IsMemcpyOk<A>::value, MemcpyPolicy, + absl::conditional_t<IsSwapOk<A>::value, ElementwiseSwapPolicy, + ElementwiseConstructPolicy>>; + static SizeType<A> NextCapacity(SizeType<A> current_capacity) { return current_capacity * 2; } @@ -473,6 +492,13 @@ class Storage { Inlined inlined; }; + void SwapN(ElementwiseSwapPolicy, Storage* other, SizeType<A> n); + void SwapN(ElementwiseConstructPolicy, Storage* other, SizeType<A> n); + + void SwapInlinedElements(MemcpyPolicy, Storage* other); + template <typename NotMemcpyPolicy> + void SwapInlinedElements(NotMemcpyPolicy, Storage* other); + template <typename... Args> ABSL_ATTRIBUTE_NOINLINE Reference<A> EmplaceBackSlow(Args&&... args); @@ -886,26 +912,7 @@ auto Storage<T, N, A>::Swap(Storage* other_storage_ptr) -> void { if (GetIsAllocated() && other_storage_ptr->GetIsAllocated()) { swap(data_.allocated, other_storage_ptr->data_.allocated); } else if (!GetIsAllocated() && !other_storage_ptr->GetIsAllocated()) { - Storage* small_ptr = this; - Storage* large_ptr = other_storage_ptr; - if (small_ptr->GetSize() > large_ptr->GetSize()) swap(small_ptr, large_ptr); - - for (SizeType<A> i = 0; i < small_ptr->GetSize(); ++i) { - swap(small_ptr->GetInlinedData()[i], large_ptr->GetInlinedData()[i]); - } - - IteratorValueAdapter<A, MoveIterator<A>> move_values( - MoveIterator<A>(large_ptr->GetInlinedData() + small_ptr->GetSize())); - - ConstructElements<A>(large_ptr->GetAllocator(), - small_ptr->GetInlinedData() + small_ptr->GetSize(), - move_values, - large_ptr->GetSize() - small_ptr->GetSize()); - - DestroyAdapter<A>::DestroyElements( - large_ptr->GetAllocator(), - large_ptr->GetInlinedData() + small_ptr->GetSize(), - large_ptr->GetSize() - small_ptr->GetSize()); + SwapInlinedElements(SwapPolicy{}, other_storage_ptr); } else { Storage* allocated_ptr = this; Storage* inlined_ptr = other_storage_ptr; @@ -941,6 +948,68 @@ auto Storage<T, N, A>::Swap(Storage* other_storage_ptr) -> void { swap(GetAllocator(), other_storage_ptr->GetAllocator()); } +template <typename T, size_t N, typename A> +void Storage<T, N, A>::SwapN(ElementwiseSwapPolicy, Storage* other, + SizeType<A> n) { + std::swap_ranges(GetInlinedData(), GetInlinedData() + n, + other->GetInlinedData()); +} + +template <typename T, size_t N, typename A> +void Storage<T, N, A>::SwapN(ElementwiseConstructPolicy, Storage* other, + SizeType<A> n) { + Pointer<A> a = GetInlinedData(); + Pointer<A> b = other->GetInlinedData(); + // see note on allocators in `SwapInlinedElements`. + A& allocator_a = GetAllocator(); + A& allocator_b = other->GetAllocator(); + for (SizeType<A> i = 0; i < n; ++i, ++a, ++b) { + ValueType<A> tmp(std::move(*a)); + + AllocatorTraits<A>::destroy(allocator_a, a); + AllocatorTraits<A>::construct(allocator_b, a, std::move(*b)); + + AllocatorTraits<A>::destroy(allocator_b, b); + AllocatorTraits<A>::construct(allocator_a, b, std::move(tmp)); + } +} + +template <typename T, size_t N, typename A> +void Storage<T, N, A>::SwapInlinedElements(MemcpyPolicy, Storage* other) { + Data tmp = data_; + data_ = other->data_; + other->data_ = tmp; +} + +template <typename T, size_t N, typename A> +template <typename NotMemcpyPolicy> +void Storage<T, N, A>::SwapInlinedElements(NotMemcpyPolicy policy, + Storage* other) { + // Note: `destroy` needs to use pre-swap allocator while `construct` - + // post-swap allocator. Allocators will be swaped later on outside of + // `SwapInlinedElements`. + Storage* small_ptr = this; + Storage* large_ptr = other; + if (small_ptr->GetSize() > large_ptr->GetSize()) { + std::swap(small_ptr, large_ptr); + } + + auto small_size = small_ptr->GetSize(); + auto diff = large_ptr->GetSize() - small_size; + SwapN(policy, other, small_size); + + IteratorValueAdapter<A, MoveIterator<A>> move_values( + MoveIterator<A>(large_ptr->GetInlinedData() + small_size)); + + ConstructElements<A>(large_ptr->GetAllocator(), + small_ptr->GetInlinedData() + small_size, move_values, + diff); + + DestroyAdapter<A>::DestroyElements(large_ptr->GetAllocator(), + large_ptr->GetInlinedData() + small_size, + diff); +} + // End ignore "array-bounds" #if !defined(__clang__) && defined(__GNUC__) #pragma GCC diagnostic pop diff --git a/absl/container/internal/raw_hash_set.h b/absl/container/internal/raw_hash_set.h index 93de2221..676cebd7 100644 --- a/absl/container/internal/raw_hash_set.h +++ b/absl/container/internal/raw_hash_set.h @@ -797,15 +797,22 @@ size_t SelectBucketCountForIterRange(InputIter first, InputIter last, return 0; } -#define ABSL_INTERNAL_ASSERT_IS_FULL(ctrl, msg) \ - ABSL_HARDENING_ASSERT((ctrl != nullptr && IsFull(*ctrl)) && msg) +#define ABSL_INTERNAL_ASSERT_IS_FULL(ctrl, operation) \ + do { \ + ABSL_HARDENING_ASSERT( \ + (ctrl != nullptr) && operation \ + " called on invalid iterator. The iterator might be an end() " \ + "iterator or may have been default constructed."); \ + ABSL_HARDENING_ASSERT( \ + (IsFull(*ctrl)) && operation \ + " called on invalid iterator. The element might have been erased or " \ + "the table might have rehashed."); \ + } while (0) inline void AssertIsValid(ctrl_t* ctrl) { - ABSL_HARDENING_ASSERT( - (ctrl == nullptr || IsFull(*ctrl)) && - "Invalid operation on iterator. The element might have " - "been erased, the table might have rehashed, or this may " - "be an end() iterator."); + ABSL_HARDENING_ASSERT((ctrl == nullptr || IsFull(*ctrl)) && + "Invalid operation on iterator. The element might have " + "been erased or the table might have rehashed."); } struct FindInfo { @@ -1034,22 +1041,19 @@ class raw_hash_set { // PRECONDITION: not an end() iterator. reference operator*() const { - ABSL_INTERNAL_ASSERT_IS_FULL(ctrl_, - "operator*() called on invalid iterator."); + ABSL_INTERNAL_ASSERT_IS_FULL(ctrl_, "operator*()"); return PolicyTraits::element(slot_); } // PRECONDITION: not an end() iterator. pointer operator->() const { - ABSL_INTERNAL_ASSERT_IS_FULL(ctrl_, - "operator-> called on invalid iterator."); + ABSL_INTERNAL_ASSERT_IS_FULL(ctrl_, "operator->"); return &operator*(); } // PRECONDITION: not an end() iterator. iterator& operator++() { - ABSL_INTERNAL_ASSERT_IS_FULL(ctrl_, - "operator++ called on invalid iterator."); + ABSL_INTERNAL_ASSERT_IS_FULL(ctrl_, "operator++"); ++ctrl_; ++slot_; skip_empty_or_deleted(); @@ -1081,7 +1085,7 @@ class raw_hash_set { // Fixes up `ctrl_` to point to a full by advancing it and `slot_` until // they reach one. // - // If a sentinel is reached, we null both of them out instead. + // If a sentinel is reached, we null `ctrl_` out instead. void skip_empty_or_deleted() { while (IsEmptyOrDeleted(*ctrl_)) { uint32_t shift = Group{ctrl_}.CountLeadingEmptyOrDeleted(); @@ -1601,8 +1605,7 @@ class raw_hash_set { // This overload is necessary because otherwise erase<K>(const K&) would be // a better match if non-const iterator is passed as an argument. void erase(iterator it) { - ABSL_INTERNAL_ASSERT_IS_FULL(it.ctrl_, - "erase() called on invalid iterator."); + ABSL_INTERNAL_ASSERT_IS_FULL(it.ctrl_, "erase()"); PolicyTraits::destroy(&alloc_ref(), it.slot_); erase_meta_only(it); } @@ -1636,8 +1639,7 @@ class raw_hash_set { } node_type extract(const_iterator position) { - ABSL_INTERNAL_ASSERT_IS_FULL(position.inner_.ctrl_, - "extract() called on invalid iterator."); + ABSL_INTERNAL_ASSERT_IS_FULL(position.inner_.ctrl_, "extract()"); auto node = CommonAccess::Transfer<node_type>(alloc_ref(), position.inner_.slot_); erase_meta_only(position); diff --git a/absl/container/internal/raw_hash_set_test.cc b/absl/container/internal/raw_hash_set_test.cc index f77ffbc1..6478d3fc 100644 --- a/absl/container/internal/raw_hash_set_test.cc +++ b/absl/container/internal/raw_hash_set_test.cc @@ -14,17 +14,25 @@ #include "absl/container/internal/raw_hash_set.h" +#include <algorithm> #include <atomic> #include <cmath> #include <cstdint> #include <deque> #include <functional> +#include <iterator> +#include <list> +#include <map> #include <memory> #include <numeric> +#include <ostream> #include <random> #include <string> +#include <type_traits> #include <unordered_map> #include <unordered_set> +#include <utility> +#include <vector> #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -33,10 +41,13 @@ #include "absl/base/internal/cycleclock.h" #include "absl/base/internal/prefetch.h" #include "absl/base/internal/raw_logging.h" +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" #include "absl/container/internal/container_memory.h" #include "absl/container/internal/hash_function_defaults.h" #include "absl/container/internal/hash_policy_testing.h" #include "absl/container/internal/hashtable_debug.h" +#include "absl/log/log.h" #include "absl/strings/string_view.h" namespace absl { @@ -339,7 +350,7 @@ class StringPolicy { struct ctor {}; template <class... Ts> - slot_type(ctor, Ts&&... ts) : pair(std::forward<Ts>(ts)...) {} + explicit slot_type(ctor, Ts&&... ts) : pair(std::forward<Ts>(ts)...) {} std::pair<std::string, std::string> pair; }; @@ -411,7 +422,7 @@ struct CustomAlloc : std::allocator<T> { CustomAlloc() {} template <typename U> - CustomAlloc(const CustomAlloc<U>& other) {} + explicit CustomAlloc(const CustomAlloc<U>& /*other*/) {} template<class U> struct rebind { using other = CustomAlloc<U>; @@ -1275,6 +1286,7 @@ TEST(Table, DISABLED_EnsureNonQuadraticTopNXorSeedByProbeSeqLength) { for (size_t size : sizes) { auto& stat = stats[size]; VerifyStats(size, expected, stat); + LOG(INFO) << size << " " << stat; } } @@ -1370,6 +1382,7 @@ TEST(Table, DISABLED_EnsureNonQuadraticTopNLinearTransformByProbeSeqLength) { for (size_t size : sizes) { auto& stat = stats[size]; VerifyStats(size, expected, stat); + LOG(INFO) << size << " " << stat; } } @@ -1504,7 +1517,7 @@ TEST(Table, RehashZeroForcesRehash) { TEST(Table, ConstructFromInitList) { using P = std::pair<std::string, std::string>; struct Q { - operator P() const { return {}; } + operator P() const { return {}; } // NOLINT }; StringTable t = {P(), Q(), {}, {{}, {}}}; } @@ -2023,20 +2036,59 @@ TEST(Table, UnstablePointers) { EXPECT_NE(old_ptr, addr(0)); } -// Confirm that we assert if we try to erase() end(). -TEST(TableDeathTest, EraseOfEndAsserts) { +bool IsAssertEnabled() { // Use an assert with side-effects to figure out if they are actually enabled. bool assert_enabled = false; - assert([&]() { + assert([&]() { // NOLINT assert_enabled = true; return true; }()); - if (!assert_enabled) return; + return assert_enabled; +} + +TEST(TableDeathTest, InvalidIteratorAsserts) { + if (!IsAssertEnabled()) GTEST_SKIP() << "Assertions not enabled."; + + IntTable t; + // Extra simple "regexp" as regexp support is highly varied across platforms. + EXPECT_DEATH_IF_SUPPORTED( + t.erase(t.end()), + "erase.* called on invalid iterator. The iterator might be an " + "end.*iterator or may have been default constructed."); + typename IntTable::iterator iter; + EXPECT_DEATH_IF_SUPPORTED( + ++iter, + "operator.* called on invalid iterator. The iterator might be an " + "end.*iterator or may have been default constructed."); + t.insert(0); + iter = t.begin(); + t.erase(iter); + EXPECT_DEATH_IF_SUPPORTED( + ++iter, + "operator.* called on invalid iterator. The element might have been " + "erased or .*the table might have rehashed."); +} + +TEST(TableDeathTest, IteratorInvalidAssertsEqualityOperator) { + if (!IsAssertEnabled()) GTEST_SKIP() << "Assertions not enabled."; IntTable t; + t.insert(1); + t.insert(2); + t.insert(3); + auto iter1 = t.begin(); + auto iter2 = std::next(iter1); + ASSERT_NE(iter1, t.end()); + ASSERT_NE(iter2, t.end()); + t.erase(iter1); // Extra simple "regexp" as regexp support is highly varied across platforms. - constexpr char kDeathMsg[] = "erase.. called on invalid iterator"; - EXPECT_DEATH_IF_SUPPORTED(t.erase(t.end()), kDeathMsg); + const char* const kDeathMessage = + "Invalid operation on iterator. The element might have .*been erased or " + "the table might have rehashed."; + EXPECT_DEATH_IF_SUPPORTED(void(iter1 == iter2), kDeathMessage); + EXPECT_DEATH_IF_SUPPORTED(void(iter2 != iter1), kDeathMessage); + t.erase(iter2); + EXPECT_DEATH_IF_SUPPORTED(void(iter1 == iter2), kDeathMessage); } #if defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE) @@ -2047,7 +2099,7 @@ TEST(RawHashSamplerTest, Sample) { auto& sampler = GlobalHashtablezSampler(); size_t start_size = 0; - std::unordered_set<const HashtablezInfo*> preexisting_info; + absl::flat_hash_set<const HashtablezInfo*> preexisting_info; start_size += sampler.Iterate([&](const HashtablezInfo& info) { preexisting_info.insert(&info); ++start_size; @@ -2074,8 +2126,8 @@ TEST(RawHashSamplerTest, Sample) { } } size_t end_size = 0; - std::unordered_map<size_t, int> observed_checksums; - std::unordered_map<ssize_t, int> reservations; + absl::flat_hash_map<size_t, int> observed_checksums; + absl::flat_hash_map<ssize_t, int> reservations; end_size += sampler.Iterate([&](const HashtablezInfo& info) { if (preexisting_info.count(&info) == 0) { observed_checksums[info.hashes_bitwise_xor.load( diff --git a/absl/copts/AbseilConfigureCopts.cmake b/absl/copts/AbseilConfigureCopts.cmake index 73435e99..f728c0e5 100644 --- a/absl/copts/AbseilConfigureCopts.cmake +++ b/absl/copts/AbseilConfigureCopts.cmake @@ -67,7 +67,6 @@ elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "arm.*|aarch64") message(WARNING "Value of CMAKE_SIZEOF_VOID_P (${CMAKE_SIZEOF_VOID_P}) is not supported.") endif() else() - message(WARNING "Value of CMAKE_SYSTEM_PROCESSOR (${CMAKE_SYSTEM_PROCESSOR}) is unknown and cannot be used to set ABSL_RANDOM_RANDEN_COPTS") set(ABSL_RANDOM_RANDEN_COPTS "") endif() diff --git a/absl/copts/GENERATED_AbseilCopts.cmake b/absl/copts/GENERATED_AbseilCopts.cmake index a4ab1aa2..ba70ef9b 100644 --- a/absl/copts/GENERATED_AbseilCopts.cmake +++ b/absl/copts/GENERATED_AbseilCopts.cmake @@ -13,22 +13,21 @@ list(APPEND ABSL_CLANG_CL_FLAGS ) list(APPEND ABSL_CLANG_CL_TEST_FLAGS - "-Wno-c99-extensions" "-Wno-deprecated-declarations" - "-Wno-missing-noreturn" + "-Wno-implicit-int-conversion" "-Wno-missing-prototypes" "-Wno-missing-variable-declarations" - "-Wno-null-conversion" "-Wno-shadow" - "-Wno-shift-sign-overflow" + "-Wno-shorten-64-to-32" "-Wno-sign-compare" + "-Wno-sign-conversion" + "-Wno-unreachable-code-loop-increment" "-Wno-unused-function" "-Wno-unused-member-function" "-Wno-unused-parameter" "-Wno-unused-private-field" "-Wno-unused-template" "-Wno-used-but-marked-unused" - "-Wno-zero-as-null-pointer-constant" "-Wno-gnu-zero-variadic-macro-arguments" ) @@ -51,7 +50,6 @@ list(APPEND ABSL_GCC_FLAGS ) list(APPEND ABSL_GCC_TEST_FLAGS - "-Wno-conversion-null" "-Wno-deprecated-declarations" "-Wno-missing-declarations" "-Wno-sign-compare" @@ -80,6 +78,7 @@ list(APPEND ABSL_LLVM_FLAGS "-Wshadow-all" "-Wstring-conversion" "-Wtautological-overlap-compare" + "-Wtautological-unsigned-zero-compare" "-Wundef" "-Wuninitialized" "-Wunreachable-code" @@ -91,30 +90,26 @@ list(APPEND ABSL_LLVM_FLAGS "-Wno-float-conversion" "-Wno-implicit-float-conversion" "-Wno-implicit-int-float-conversion" - "-Wno-implicit-int-conversion" - "-Wno-shorten-64-to-32" - "-Wno-sign-conversion" "-Wno-unknown-warning-option" "-DNOMINMAX" ) list(APPEND ABSL_LLVM_TEST_FLAGS - "-Wno-c99-extensions" "-Wno-deprecated-declarations" - "-Wno-missing-noreturn" + "-Wno-implicit-int-conversion" "-Wno-missing-prototypes" "-Wno-missing-variable-declarations" - "-Wno-null-conversion" "-Wno-shadow" - "-Wno-shift-sign-overflow" + "-Wno-shorten-64-to-32" "-Wno-sign-compare" + "-Wno-sign-conversion" + "-Wno-unreachable-code-loop-increment" "-Wno-unused-function" "-Wno-unused-member-function" "-Wno-unused-parameter" "-Wno-unused-private-field" "-Wno-unused-template" "-Wno-used-but-marked-unused" - "-Wno-zero-as-null-pointer-constant" "-Wno-gnu-zero-variadic-macro-arguments" ) diff --git a/absl/copts/GENERATED_copts.bzl b/absl/copts/GENERATED_copts.bzl index a6efc98e..62aab656 100644 --- a/absl/copts/GENERATED_copts.bzl +++ b/absl/copts/GENERATED_copts.bzl @@ -14,22 +14,21 @@ ABSL_CLANG_CL_FLAGS = [ ] ABSL_CLANG_CL_TEST_FLAGS = [ - "-Wno-c99-extensions", "-Wno-deprecated-declarations", - "-Wno-missing-noreturn", + "-Wno-implicit-int-conversion", "-Wno-missing-prototypes", "-Wno-missing-variable-declarations", - "-Wno-null-conversion", "-Wno-shadow", - "-Wno-shift-sign-overflow", + "-Wno-shorten-64-to-32", "-Wno-sign-compare", + "-Wno-sign-conversion", + "-Wno-unreachable-code-loop-increment", "-Wno-unused-function", "-Wno-unused-member-function", "-Wno-unused-parameter", "-Wno-unused-private-field", "-Wno-unused-template", "-Wno-used-but-marked-unused", - "-Wno-zero-as-null-pointer-constant", "-Wno-gnu-zero-variadic-macro-arguments", ] @@ -52,7 +51,6 @@ ABSL_GCC_FLAGS = [ ] ABSL_GCC_TEST_FLAGS = [ - "-Wno-conversion-null", "-Wno-deprecated-declarations", "-Wno-missing-declarations", "-Wno-sign-compare", @@ -81,6 +79,7 @@ ABSL_LLVM_FLAGS = [ "-Wshadow-all", "-Wstring-conversion", "-Wtautological-overlap-compare", + "-Wtautological-unsigned-zero-compare", "-Wundef", "-Wuninitialized", "-Wunreachable-code", @@ -92,30 +91,26 @@ ABSL_LLVM_FLAGS = [ "-Wno-float-conversion", "-Wno-implicit-float-conversion", "-Wno-implicit-int-float-conversion", - "-Wno-implicit-int-conversion", - "-Wno-shorten-64-to-32", - "-Wno-sign-conversion", "-Wno-unknown-warning-option", "-DNOMINMAX", ] ABSL_LLVM_TEST_FLAGS = [ - "-Wno-c99-extensions", "-Wno-deprecated-declarations", - "-Wno-missing-noreturn", + "-Wno-implicit-int-conversion", "-Wno-missing-prototypes", "-Wno-missing-variable-declarations", - "-Wno-null-conversion", "-Wno-shadow", - "-Wno-shift-sign-overflow", + "-Wno-shorten-64-to-32", "-Wno-sign-compare", + "-Wno-sign-conversion", + "-Wno-unreachable-code-loop-increment", "-Wno-unused-function", "-Wno-unused-member-function", "-Wno-unused-parameter", "-Wno-unused-private-field", "-Wno-unused-template", "-Wno-used-but-marked-unused", - "-Wno-zero-as-null-pointer-constant", "-Wno-gnu-zero-variadic-macro-arguments", ] diff --git a/absl/copts/copts.py b/absl/copts/copts.py index 0d6c1ec3..732af9ea 100644 --- a/absl/copts/copts.py +++ b/absl/copts/copts.py @@ -17,22 +17,21 @@ MSVC_BIG_WARNING_FLAGS = [ ] LLVM_TEST_DISABLE_WARNINGS_FLAGS = [ - "-Wno-c99-extensions", "-Wno-deprecated-declarations", - "-Wno-missing-noreturn", + "-Wno-implicit-int-conversion", "-Wno-missing-prototypes", "-Wno-missing-variable-declarations", - "-Wno-null-conversion", "-Wno-shadow", - "-Wno-shift-sign-overflow", + "-Wno-shorten-64-to-32", "-Wno-sign-compare", + "-Wno-sign-conversion", + "-Wno-unreachable-code-loop-increment", "-Wno-unused-function", "-Wno-unused-member-function", "-Wno-unused-parameter", "-Wno-unused-private-field", "-Wno-unused-template", "-Wno-used-but-marked-unused", - "-Wno-zero-as-null-pointer-constant", # gtest depends on this GNU extension being offered. "-Wno-gnu-zero-variadic-macro-arguments", ] @@ -68,7 +67,6 @@ COPT_VARS = { "-DNOMINMAX", ], "ABSL_GCC_TEST_FLAGS": [ - "-Wno-conversion-null", "-Wno-deprecated-declarations", "-Wno-missing-declarations", "-Wno-sign-compare", @@ -96,6 +94,7 @@ COPT_VARS = { "-Wshadow-all", "-Wstring-conversion", "-Wtautological-overlap-compare", + "-Wtautological-unsigned-zero-compare", "-Wundef", "-Wuninitialized", "-Wunreachable-code", @@ -109,9 +108,6 @@ COPT_VARS = { "-Wno-float-conversion", "-Wno-implicit-float-conversion", "-Wno-implicit-int-float-conversion", - "-Wno-implicit-int-conversion", - "-Wno-shorten-64-to-32", - "-Wno-sign-conversion", # Disable warnings on unknown warning flags (when warning flags are # unknown on older compiler versions) "-Wno-unknown-warning-option", diff --git a/absl/debugging/CMakeLists.txt b/absl/debugging/CMakeLists.txt index 051e7017..e823f15b 100644 --- a/absl/debugging/CMakeLists.txt +++ b/absl/debugging/CMakeLists.txt @@ -62,7 +62,7 @@ absl_cc_library( ${ABSL_DEFAULT_COPTS} LINKOPTS ${ABSL_DEFAULT_LINKOPTS} - $<$<BOOL:${MINGW}>:"dbghelp"> + $<$<BOOL:${MINGW}>:-ldbghelp> DEPS absl::debugging_internal absl::demangle_internal diff --git a/absl/debugging/failure_signal_handler.cc b/absl/debugging/failure_signal_handler.cc index 5e8f0b05..ef8ab9e5 100644 --- a/absl/debugging/failure_signal_handler.cc +++ b/absl/debugging/failure_signal_handler.cc @@ -138,7 +138,8 @@ static bool SetupAlternateStackOnce() { const size_t page_mask = static_cast<size_t>(sysconf(_SC_PAGESIZE)) - 1; #endif size_t stack_size = - (std::max<size_t>(SIGSTKSZ, 65536) + page_mask) & ~page_mask; + (std::max(static_cast<size_t>(SIGSTKSZ), size_t{65536}) + page_mask) & + ~page_mask; #if defined(ABSL_HAVE_ADDRESS_SANITIZER) || \ defined(ABSL_HAVE_MEMORY_SANITIZER) || defined(ABSL_HAVE_THREAD_SANITIZER) // Account for sanitizer instrumentation requiring additional stack space. diff --git a/absl/debugging/internal/stacktrace_aarch64-inl.inc b/absl/debugging/internal/stacktrace_aarch64-inl.inc index 891942c0..71cdaf09 100644 --- a/absl/debugging/internal/stacktrace_aarch64-inl.inc +++ b/absl/debugging/internal/stacktrace_aarch64-inl.inc @@ -19,7 +19,7 @@ #include "absl/debugging/internal/vdso_support.h" // a no-op on non-elf or non-glibc systems #include "absl/debugging/stacktrace.h" -static const uintptr_t kUnknownFrameSize = 0; +static const size_t kUnknownFrameSize = 0; #if defined(__linux__) // Returns the address of the VDSO __kernel_rt_sigreturn function, if present. @@ -65,11 +65,12 @@ static const unsigned char* GetKernelRtSigreturnAddress() { // Compute the size of a stack frame in [low..high). We assume that // low < high. Return size of kUnknownFrameSize. template<typename T> -static inline uintptr_t ComputeStackFrameSize(const T* low, - const T* high) { +static inline size_t ComputeStackFrameSize(const T* low, + const T* high) { const char* low_char_ptr = reinterpret_cast<const char *>(low); const char* high_char_ptr = reinterpret_cast<const char *>(high); - return low < high ? high_char_ptr - low_char_ptr : kUnknownFrameSize; + return low < high ? static_cast<size_t>(high_char_ptr - low_char_ptr) + : kUnknownFrameSize; } // Given a pointer to a stack frame, locate and return the calling @@ -117,8 +118,8 @@ static void **NextStackFrame(void **old_frame_pointer, const void *uc) { // Check frame size. In strict mode, we assume frames to be under // 100,000 bytes. In non-strict mode, we relax the limit to 1MB. if (check_frame_size) { - const uintptr_t max_size = STRICT_UNWINDING ? 100000 : 1000000; - const uintptr_t frame_size = + const size_t max_size = STRICT_UNWINDING ? 100000 : 1000000; + const size_t frame_size = ComputeStackFrameSize(old_frame_pointer, new_frame_pointer); if (frame_size == kUnknownFrameSize || frame_size > max_size) return nullptr; @@ -165,7 +166,8 @@ static int UnwindImpl(void** result, int* sizes, int max_depth, int skip_count, } else { result[n] = prev_return_address; if (IS_STACK_FRAMES) { - sizes[n] = ComputeStackFrameSize(frame_pointer, next_frame_pointer); + sizes[n] = static_cast<int>( + ComputeStackFrameSize(frame_pointer, next_frame_pointer)); } n++; } diff --git a/absl/functional/any_invocable.h b/absl/functional/any_invocable.h index 040418d4..3e783c87 100644 --- a/absl/functional/any_invocable.h +++ b/absl/functional/any_invocable.h @@ -148,6 +148,9 @@ ABSL_NAMESPACE_BEGIN // // rvalue-reference qualified. // std::move(continuation)(result_of_foo); // } +// +// Attempting to call `absl::AnyInvocable` multiple times in such a case +// results in undefined behavior. template <class Sig> class AnyInvocable : private internal_any_invocable::Impl<Sig> { private: diff --git a/absl/functional/any_invocable_test.cc b/absl/functional/any_invocable_test.cc index dabaae9b..1ed85407 100644 --- a/absl/functional/any_invocable_test.cc +++ b/absl/functional/any_invocable_test.cc @@ -16,6 +16,7 @@ #include <cstddef> #include <initializer_list> +#include <memory> #include <numeric> #include <type_traits> @@ -1156,9 +1157,6 @@ TYPED_TEST_P(AnyInvTestMovable, ConversionConstructionUserDefinedType) { EXPECT_TRUE(static_cast<bool>(fun)); EXPECT_EQ(29, TypeParam::ToThisParam(fun)(7, 8, 9).value); - - EXPECT_TRUE(static_cast<bool>(fun)); - EXPECT_EQ(38, TypeParam::ToThisParam(fun)(10, 11, 12).value); } TYPED_TEST_P(AnyInvTestMovable, ConversionConstructionVoidCovariance) { @@ -1179,9 +1177,6 @@ TYPED_TEST_P(AnyInvTestMovable, ConversionAssignUserDefinedTypeEmptyLhs) { EXPECT_TRUE(static_cast<bool>(fun)); EXPECT_EQ(29, TypeParam::ToThisParam(fun)(7, 8, 9).value); - - EXPECT_TRUE(static_cast<bool>(fun)); - EXPECT_EQ(38, TypeParam::ToThisParam(fun)(10, 11, 12).value); } TYPED_TEST_P(AnyInvTestMovable, ConversionAssignUserDefinedTypeNonemptyLhs) { @@ -1193,9 +1188,6 @@ TYPED_TEST_P(AnyInvTestMovable, ConversionAssignUserDefinedTypeNonemptyLhs) { EXPECT_TRUE(static_cast<bool>(fun)); EXPECT_EQ(29, TypeParam::ToThisParam(fun)(7, 8, 9).value); - - EXPECT_TRUE(static_cast<bool>(fun)); - EXPECT_EQ(38, TypeParam::ToThisParam(fun)(10, 11, 12).value); } TYPED_TEST_P(AnyInvTestMovable, ConversionAssignVoidCovariance) { @@ -1414,6 +1406,41 @@ TYPED_TEST_P(AnyInvTestRvalue, ConversionAssignReferenceWrapper) { std::is_assignable<AnyInvType&, std::reference_wrapper<AddType>>::value)); } +TYPED_TEST_P(AnyInvTestRvalue, NonConstCrashesOnSecondCall) { + using AnyInvType = typename TypeParam::AnyInvType; + using AddType = typename TypeParam::AddType; + + AnyInvType fun(absl::in_place_type<AddType>, 5); + + EXPECT_TRUE(static_cast<bool>(fun)); + std::move(fun)(7, 8, 9); + + // Ensure we're still valid + EXPECT_TRUE(static_cast<bool>(fun)); // NOLINT(bugprone-use-after-move) + +#if !defined(NDEBUG) || ABSL_OPTION_HARDENED == 1 + EXPECT_DEATH_IF_SUPPORTED(std::move(fun)(7, 8, 9), ""); +#endif +} + +// Ensure that any qualifiers (in particular &&-qualifiers) do not affect +// when the destructor is actually run. +TYPED_TEST_P(AnyInvTestRvalue, QualifierIndependentObjectLifetime) { + using AnyInvType = typename TypeParam::AnyInvType; + + auto refs = std::make_shared<std::nullptr_t>(); + { + AnyInvType fun([refs](auto&&...) noexcept { return 0; }); + EXPECT_FALSE(refs.unique()); + + std::move(fun)(7, 8, 9); + + // Ensure destructor hasn't run even if rref-qualified + EXPECT_FALSE(refs.unique()); + } + EXPECT_TRUE(refs.unique()); +} + // NOTE: This test suite originally attempted to enumerate all possible // combinations of type properties but the build-time started getting too large. // Instead, it is now assumed that certain parameters are orthogonal and so @@ -1670,7 +1697,9 @@ INSTANTIATE_TYPED_TEST_SUITE_P(NonRvalueCallNothrow, AnyInvTestNonRvalue, REGISTER_TYPED_TEST_SUITE_P(AnyInvTestRvalue, ConversionConstructionReferenceWrapper, NonMoveableResultType, - ConversionAssignReferenceWrapper); + ConversionAssignReferenceWrapper, + NonConstCrashesOnSecondCall, + QualifierIndependentObjectLifetime); INSTANTIATE_TYPED_TEST_SUITE_P(RvalueCallMayThrow, AnyInvTestRvalue, TestParameterListRvalueQualifiersCallMayThrow); diff --git a/absl/functional/internal/any_invocable.h b/absl/functional/internal/any_invocable.h index 35b389d1..8fce4bf6 100644 --- a/absl/functional/internal/any_invocable.h +++ b/absl/functional/internal/any_invocable.h @@ -809,11 +809,31 @@ using CanAssignReferenceWrapper = TrueAlias< : Core(absl::in_place_type<absl::decay_t<T> inv_quals>, \ std::forward<Args>(args)...) {} \ \ + InvokerType<noex, ReturnType, P...>* ExtractInvoker() cv { \ + using QualifiedTestType = int cv ref; \ + auto* invoker = this->invoker_; \ + if (!std::is_const<QualifiedTestType>::value && \ + std::is_rvalue_reference<QualifiedTestType>::value) { \ + ABSL_HARDENING_ASSERT([this]() { \ + /* We checked that this isn't const above, so const_cast is safe */ \ + const_cast<Impl*>(this)->invoker_ = \ + [](TypeErasedState*, \ + ForwardedParameterType<P>...) noexcept(noex) -> ReturnType { \ + ABSL_HARDENING_ASSERT(false && "AnyInvocable use-after-move"); \ + std::terminate(); \ + }; \ + return this->HasValue(); \ + }()); \ + } \ + return invoker; \ + } \ + \ /*The actual invocation operation with the proper signature*/ \ ReturnType operator()(P... args) cv ref noexcept(noex) { \ assert(this->invoker_ != nullptr); \ - return this->invoker_(const_cast<TypeErasedState*>(&this->state_), \ - static_cast<ForwardedParameterType<P>>(args)...); \ + return this->ExtractInvoker()( \ + const_cast<TypeErasedState*>(&this->state_), \ + static_cast<ForwardedParameterType<P>>(args)...); \ } \ } diff --git a/absl/log/BUILD.bazel b/absl/log/BUILD.bazel index dadc8856..16788ae2 100644 --- a/absl/log/BUILD.bazel +++ b/absl/log/BUILD.bazel @@ -324,6 +324,7 @@ cc_test( "//absl/log/internal:config", "//absl/log/internal:test_matchers", "//absl/strings", + "//absl/strings:str_format", "@com_google_googletest//:gtest_main", ], ) diff --git a/absl/log/CMakeLists.txt b/absl/log/CMakeLists.txt index 09e4ca0c..28d4b519 100644 --- a/absl/log/CMakeLists.txt +++ b/absl/log/CMakeLists.txt @@ -681,6 +681,7 @@ absl_cc_test( absl::log_internal_config absl::log_internal_test_matchers absl::scoped_mock_log + absl::str_format absl::strings GTest::gmock GTest::gtest_main diff --git a/absl/log/flags_test.cc b/absl/log/flags_test.cc index 7a803152..a0f6d763 100644 --- a/absl/log/flags_test.cc +++ b/absl/log/flags_test.cc @@ -48,7 +48,10 @@ class LogFlagsTest : public ::testing::Test { absl::FlagSaver flag_saver_; }; -TEST_F(LogFlagsTest, StderrKnobsDefault) { +// This test is disabled because it adds order dependency to the test suite. +// This order dependency is currently not fixable due to the way the +// stderrthreshold global value is out of sync with the stderrthreshold flag. +TEST_F(LogFlagsTest, DISABLED_StderrKnobsDefault) { EXPECT_EQ(absl::StderrThreshold(), DefaultStderrThreshold()); } diff --git a/absl/log/internal/conditions.cc b/absl/log/internal/conditions.cc index 70f2acef..a9f4966f 100644 --- a/absl/log/internal/conditions.cc +++ b/absl/log/internal/conditions.cc @@ -37,7 +37,7 @@ uint32_t LossyIncrement(std::atomic<uint32_t>* counter) { } // namespace bool LogEveryNState::ShouldLog(int n) { - return n != 0 && (LossyIncrement(&counter_) % n) == 0; + return n > 0 && (LossyIncrement(&counter_) % static_cast<uint32_t>(n)) == 0; } bool LogFirstNState::ShouldLog(int n) { diff --git a/absl/log/internal/log_format.cc b/absl/log/internal/log_format.cc index b10a656b..5b280a2d 100644 --- a/absl/log/internal/log_format.cc +++ b/absl/log/internal/log_format.cc @@ -46,6 +46,31 @@ ABSL_NAMESPACE_BEGIN namespace log_internal { namespace { +// This templated function avoids compiler warnings about tautological +// comparisons when log_internal::Tid is unsigned. It can be replaced with a +// constexpr if once the minimum C++ version Abseil suppports is C++17. +template <typename T> +inline std::enable_if_t<!std::is_signed<T>::value> +PutLeadingWhitespace(T tid, char*& p) { + if (tid < 10) *p++ = ' '; + if (tid < 100) *p++ = ' '; + if (tid < 1000) *p++ = ' '; + if (tid < 10000) *p++ = ' '; + if (tid < 100000) *p++ = ' '; + if (tid < 1000000) *p++ = ' '; +} + +template <typename T> +inline std::enable_if_t<std::is_signed<T>::value> +PutLeadingWhitespace(T tid, char*& p) { + if (tid >= 0 && tid < 10) *p++ = ' '; + if (tid > -10 && tid < 100) *p++ = ' '; + if (tid > -100 && tid < 1000) *p++ = ' '; + if (tid > -1000 && tid < 10000) *p++ = ' '; + if (tid > -10000 && tid < 100000) *p++ = ' '; + if (tid > -100000 && tid < 1000000) *p++ = ' '; +} + // The fields before the filename are all fixed-width except for the thread ID, // which is of bounded width. size_t FormatBoundedFields(absl::LogSeverity severity, absl::Time timestamp, @@ -78,7 +103,7 @@ size_t FormatBoundedFields(absl::LogSeverity severity, absl::Time timestamp, absl::LogSeverityName(severity)[0], static_cast<int>(tv.tv_sec), static_cast<int>(tv.tv_usec), static_cast<int>(tid)); if (snprintf_result >= 0) { - buf.remove_prefix(snprintf_result); + buf.remove_prefix(static_cast<size_t>(snprintf_result)); return static_cast<size_t>(snprintf_result); } return 0; @@ -87,38 +112,33 @@ size_t FormatBoundedFields(absl::LogSeverity severity, absl::Time timestamp, char* p = buf.data(); *p++ = absl::LogSeverityName(severity)[0]; const absl::TimeZone::CivilInfo ci = tz->At(timestamp); - absl::numbers_internal::PutTwoDigits(ci.cs.month(), p); + absl::numbers_internal::PutTwoDigits(static_cast<size_t>(ci.cs.month()), p); p += 2; - absl::numbers_internal::PutTwoDigits(ci.cs.day(), p); + absl::numbers_internal::PutTwoDigits(static_cast<size_t>(ci.cs.day()), p); p += 2; *p++ = ' '; - absl::numbers_internal::PutTwoDigits(ci.cs.hour(), p); + absl::numbers_internal::PutTwoDigits(static_cast<size_t>(ci.cs.hour()), p); p += 2; *p++ = ':'; - absl::numbers_internal::PutTwoDigits(ci.cs.minute(), p); + absl::numbers_internal::PutTwoDigits(static_cast<size_t>(ci.cs.minute()), p); p += 2; *p++ = ':'; - absl::numbers_internal::PutTwoDigits(ci.cs.second(), p); + absl::numbers_internal::PutTwoDigits(static_cast<size_t>(ci.cs.second()), p); p += 2; *p++ = '.'; const int64_t usecs = absl::ToInt64Microseconds(ci.subsecond); - absl::numbers_internal::PutTwoDigits(usecs / 10000, p); + absl::numbers_internal::PutTwoDigits(static_cast<size_t>(usecs / 10000), p); p += 2; - absl::numbers_internal::PutTwoDigits(usecs / 100 % 100, p); + absl::numbers_internal::PutTwoDigits(static_cast<size_t>(usecs / 100 % 100), + p); p += 2; - absl::numbers_internal::PutTwoDigits(usecs % 100, p); + absl::numbers_internal::PutTwoDigits(static_cast<size_t>(usecs % 100), p); p += 2; *p++ = ' '; - constexpr bool unsigned_tid_t = !std::is_signed<log_internal::Tid>::value; - if ((unsigned_tid_t || tid >= 0) && tid < 10) *p++ = ' '; - if ((unsigned_tid_t || tid > -10) && tid < 100) *p++ = ' '; - if ((unsigned_tid_t || tid > -100) && tid < 1000) *p++ = ' '; - if ((unsigned_tid_t || tid > -1000) && tid < 10000) *p++ = ' '; - if ((unsigned_tid_t || tid > -10000) && tid < 100000) *p++ = ' '; - if ((unsigned_tid_t || tid > -100000) && tid < 1000000) *p++ = ' '; + PutLeadingWhitespace(tid, p); p = absl::numbers_internal::FastIntToBuffer(tid, p); *p++ = ' '; - const size_t bytes_formatted = p - buf.data(); + const size_t bytes_formatted = static_cast<size_t>(p - buf.data()); buf.remove_prefix(bytes_formatted); return bytes_formatted; } @@ -146,7 +166,7 @@ size_t FormatLineNumber(int line, absl::Span<char>& buf) { p = absl::numbers_internal::FastIntToBuffer(line, p); *p++ = ']'; *p++ = ' '; - const size_t bytes_formatted = p - buf.data(); + const size_t bytes_formatted = static_cast<size_t>(p - buf.data()); buf.remove_prefix(bytes_formatted); return bytes_formatted; } diff --git a/absl/log/internal/log_message.cc b/absl/log/internal/log_message.cc index 9ef0c29e..82833af0 100644 --- a/absl/log/internal/log_message.cc +++ b/absl/log/internal/log_message.cc @@ -118,20 +118,23 @@ class LogEntryStreambuf final : public std::streambuf { // If no data were ever streamed in, this is where we must write the prefix. if (pbase() == nullptr) Initialize(); // Here we reclaim the two bytes we reserved. - size_t idx = pptr() - pbase(); + ptrdiff_t idx = pptr() - pbase(); setp(buf_.data(), buf_.data() + buf_.size()); - pbump(idx); + pbump(static_cast<int>(idx)); sputc('\n'); sputc('\0'); finalized_ = true; - return absl::Span<const char>(pbase(), pptr() - pbase()); + return absl::Span<const char>(pbase(), + static_cast<size_t>(pptr() - pbase())); } size_t prefix_len() const { return prefix_len_; } protected: std::streamsize xsputn(const char* s, std::streamsize n) override { + if (n < 0) return 0; if (pbase() == nullptr) Initialize(); - return Append(absl::string_view(s, n)); + return static_cast<std::streamsize>( + Append(absl::string_view(s, static_cast<size_t>(n)))); } int overflow(int ch = EOF) override { @@ -154,14 +157,14 @@ class LogEntryStreambuf final : public std::streambuf { prefix_len_ = log_internal::FormatLogPrefix( entry_.log_severity(), entry_.timestamp(), entry_.tid(), entry_.source_basename(), entry_.source_line(), remaining); - pbump(prefix_len_); + pbump(static_cast<int>(prefix_len_)); } } size_t Append(absl::string_view data) { - absl::Span<char> remaining(pptr(), epptr() - pptr()); + absl::Span<char> remaining(pptr(), static_cast<size_t>(epptr() - pptr())); const size_t written = AppendTruncated(data, &remaining); - pbump(written); + pbump(static_cast<int>(written)); return written; } diff --git a/absl/log/internal/log_message.h b/absl/log/internal/log_message.h index 37a267c0..992bb630 100644 --- a/absl/log/internal/log_message.h +++ b/absl/log/internal/log_message.h @@ -41,6 +41,7 @@ #include "absl/log/internal/nullguard.h" #include "absl/log/log_entry.h" #include "absl/log/log_sink.h" +#include "absl/strings/internal/has_absl_stringify.h" #include "absl/strings/string_view.h" #include "absl/time/time.h" @@ -153,8 +154,17 @@ class LogMessage { template <int SIZE> LogMessage& operator<<(char (&buf)[SIZE]) ABSL_ATTRIBUTE_NOINLINE; - // Default: uses `ostream` logging to convert `v` to a string. - template <typename T> + // Types that support `AbslStringify()` are serialized that way. + template <typename T, + typename std::enable_if< + strings_internal::HasAbslStringify<T>::value, int>::type = 0> + LogMessage& operator<<(const T& v) ABSL_ATTRIBUTE_NOINLINE; + + // Types that don't support `AbslStringify()` but do support streaming into a + // `std::ostream&` are serialized that way. + template <typename T, + typename std::enable_if< + !strings_internal::HasAbslStringify<T>::value, int>::type = 0> LogMessage& operator<<(const T& v) ABSL_ATTRIBUTE_NOINLINE; // Note: We explicitly do not support `operator<<` for non-const references @@ -205,12 +215,44 @@ class LogMessage { std::ostream stream_; }; +// Helper class so that `AbslStringify()` can modify the LogMessage. +class StringifySink final { + public: + explicit StringifySink(LogMessage& message) : message_(message) {} + + void Append(size_t count, char ch) { message_ << std::string(count, ch); } + + void Append(absl::string_view v) { message_ << v; } + + // For types that implement `AbslStringify` using `absl::Format()`. + friend void AbslFormatFlush(StringifySink* sink, absl::string_view v) { + sink->Append(v); + } + + private: + LogMessage& message_; +}; + +// Note: the following is declared `ABSL_ATTRIBUTE_NOINLINE` +template <typename T, + typename std::enable_if<strings_internal::HasAbslStringify<T>::value, + int>::type> +LogMessage& LogMessage::operator<<(const T& v) { + StringifySink sink(*this); + // Replace with public API. + AbslStringify(sink, v); + return *this; +} + // Note: the following is declared `ABSL_ATTRIBUTE_NOINLINE` -template <typename T> +template <typename T, + typename std::enable_if<!strings_internal::HasAbslStringify<T>::value, + int>::type> LogMessage& LogMessage::operator<<(const T& v) { stream_ << log_internal::NullGuard<T>().Guard(v); return *this; } + inline LogMessage& LogMessage::operator<<( std::ostream& (*m)(std::ostream& os)) { stream_ << m; diff --git a/absl/log/internal/log_sink_set.cc b/absl/log/internal/log_sink_set.cc index 4fe301c6..f9d030aa 100644 --- a/absl/log/internal/log_sink_set.cc +++ b/absl/log/internal/log_sink_set.cc @@ -69,6 +69,7 @@ bool& ThreadIsLoggingStatus() { } return true; }(); + (void)unused; // Fixes -wunused-variable warning bool* thread_is_logging_ptr = reinterpret_cast<bool*>(pthread_getspecific(thread_is_logging_key)); diff --git a/absl/log/internal/test_helpers.cc b/absl/log/internal/test_helpers.cc index bff5cc17..0de5b96b 100644 --- a/absl/log/internal/test_helpers.cc +++ b/absl/log/internal/test_helpers.cc @@ -46,7 +46,7 @@ bool DiedOfFatal(int exit_status) { // Depending on NDEBUG and (configuration?) MSVC's abort either results // in error code 3 (SIGABRT) or error code 0x80000003 (breakpoint // triggered). - return ::testing::ExitedWithCode(3)(exit_status & ~0x80000000); + return ::testing::ExitedWithCode(3)(exit_status & 0x7fffffff); #elif defined(__Fuchsia__) // The Fuchsia death test implementation kill()'s the process when it detects // an exception, so it should exit with the corresponding code. See diff --git a/absl/log/log.h b/absl/log/log.h index 13c4938f..4cd52041 100644 --- a/absl/log/log.h +++ b/absl/log/log.h @@ -132,10 +132,45 @@ // as they would be if streamed into a `std::ostream`, however it should be // noted that their actual type is unspecified. // -// To implement a custom formatting operator for a type you own, define +// To implement a custom formatting operator for a type you own, there are two +// options: `AbslStringify()` or `std::ostream& operator<<(std::ostream&, ...)`. +// It is recommended that users make their types loggable through +// `AbslStringify()` as it is a universal stringification extension that also +// enables `absl::StrFormat` and `absl::StrCat` support. If both +// `AbslStringify()` and `std::ostream& operator<<(std::ostream&, ...)` are +// defined, `AbslStringify()` will be used. +// +// To use the `AbslStringify()` API, define a friend function template in your +// type's namespace with the following signature: +// +// template <typename Sink> +// void AbslStringify(Sink& sink, const UserDefinedType& value); +// +// `Sink` has the same interface as `absl::FormatSink`, but without +// `PutPaddedString()`. +// +// Example: +// +// struct Point { +// template <typename Sink> +// friend void AbslStringify(Sink& sink, const Point& p) { +// absl::Format(&sink, "(%v, %v)", p.x, p.y); +// } +// +// int x; +// int y; +// }; +// +// To use `std::ostream& operator<<(std::ostream&, ...)`, define // `std::ostream& operator<<(std::ostream&, ...)` in your type's namespace (for // ADL) just as you would to stream it to `std::cout`. // +// Currently `AbslStringify()` ignores output manipulators but this is not +// guaranteed behavior and may be subject to change in the future. If you would +// like guaranteed behavior regarding output manipulators, please use +// `std::ostream& operator<<(std::ostream&, ...)` to make custom types loggable +// instead. +// // Those macros that support streaming honor output manipulators and `fmtflag` // changes that output data (e.g. `std::ends`) or control formatting of data // (e.g. `std::hex` and `std::fixed`), however flushing such a stream is @@ -196,9 +231,6 @@ // Example: // // LOG_IF(INFO, num_cookies > 10) << "Got lots of cookies"; -// -// There is no `VLOG_IF` because the order of evaluation of the arguments is -// ambiguous and the alternate spelling with an `if`-statement is trivial. #define LOG_IF(severity, condition) \ ABSL_LOG_INTERNAL_CONDITION_##severity(STATELESS, condition) \ ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() @@ -318,42 +350,6 @@ (EveryNSec, n_seconds) ABSL_LOGGING_INTERNAL_LOG_##severity.InternalStream() #endif // def NDEBUG -#define VLOG_EVERY_N(verbose_level, n) \ - for (int absl_logging_internal_verbose_level = (verbose_level), \ - absl_logging_internal_log_loop = 1; \ - absl_logging_internal_log_loop; absl_logging_internal_log_loop = 0) \ - ABSL_LOG_INTERNAL_CONDITION_INFO( \ - STATEFUL, VLOG_IS_ON(absl_logging_internal_verbose_level)) \ - (EveryN, n) ABSL_LOGGING_INTERNAL_LOG_INFO.InternalStream().WithVerbosity( \ - absl_logging_internal_verbose_level) - -#define VLOG_FIRST_N(verbose_level, n) \ - for (int absl_logging_internal_verbose_level = (verbose_level), \ - absl_logging_internal_log_loop = 1; \ - absl_logging_internal_log_loop; absl_logging_internal_log_loop = 0) \ - ABSL_LOG_INTERNAL_CONDITION_INFO( \ - STATEFUL, VLOG_IS_ON(absl_logging_internal_verbose_level)) \ - (FirstN, n) ABSL_LOGGING_INTERNAL_LOG_INFO.InternalStream().WithVerbosity( \ - absl_logging_internal_verbose_level) - -#define VLOG_EVERY_POW_2(verbose_level) \ - for (int absl_logging_internal_verbose_level = (verbose_level), \ - absl_logging_internal_log_loop = 1; \ - absl_logging_internal_log_loop; absl_logging_internal_log_loop = 0) \ - ABSL_LOG_INTERNAL_CONDITION_INFO( \ - STATEFUL, VLOG_IS_ON(absl_logging_internal_verbose_level)) \ - (EveryPow2) ABSL_LOGGING_INTERNAL_LOG_INFO.InternalStream().WithVerbosity( \ - absl_logging_internal_verbose_level) - -#define VLOG_EVERY_N_SEC(verbose_level, n_seconds) \ - for (int absl_logging_internal_verbose_level = (verbose_level), \ - absl_logging_internal_log_loop = 1; \ - absl_logging_internal_log_loop; absl_logging_internal_log_loop = 0) \ - ABSL_LOG_INTERNAL_CONDITION_INFO( \ - STATEFUL, VLOG_IS_ON(absl_logging_internal_verbose_level)) \ - (EveryNSec, n_seconds) ABSL_LOGGING_INTERNAL_LOG_INFO.InternalStream() \ - .WithVerbosity(absl_logging_internal_verbose_level) - // `LOG_IF_EVERY_N` and friends behave as the corresponding `LOG_EVERY_N` // but neither increment a counter nor log a message if condition is false (as // `LOG_IF`). diff --git a/absl/log/log_entry.h b/absl/log/log_entry.h index d90961fe..30114c33 100644 --- a/absl/log/log_entry.h +++ b/absl/log/log_entry.h @@ -58,8 +58,10 @@ class LogEntry final { static constexpr int kNoVerbosityLevel = -1; static constexpr int kNoVerboseLevel = -1; // TO BE removed - LogEntry(const LogEntry&) = default; - LogEntry& operator=(const LogEntry&) = default; + // Pass `LogEntry` by reference, and do not store it as its state does not + // outlive the call to `LogSink::Send()`. + LogEntry(const LogEntry&) = delete; + LogEntry& operator=(const LogEntry&) = delete; // Source file and line where the log message occurred. Taken from `__FILE__` // and `__LINE__` unless overridden by `LOG(...).AtLocation(...)`. diff --git a/absl/log/log_entry_test.cc b/absl/log/log_entry_test.cc index b19794e4..7238356e 100644 --- a/absl/log/log_entry_test.cc +++ b/absl/log/log_entry_test.cc @@ -101,13 +101,14 @@ class LogEntryTestPeer { entry_.source_basename(), entry_.source_line(), view) : 0; - EXPECT_THAT(entry_.prefix_len_, Eq(view.data() - buf_.data())); + EXPECT_THAT(entry_.prefix_len_, + Eq(static_cast<size_t>(view.data() - buf_.data()))); AppendTruncated(text_message, view); view = absl::Span<char>(view.data(), view.size() + 2); view[0] = '\n'; view[1] = '\0'; view.remove_prefix(2); - buf_.resize(view.data() - buf_.data()); + buf_.resize(static_cast<size_t>(view.data() - buf_.data())); entry_.text_message_with_prefix_and_newline_and_nul_ = absl::MakeSpan(buf_); } LogEntryTestPeer(const LogEntryTestPeer&) = delete; @@ -124,7 +125,7 @@ class LogEntryTestPeer { const size_t prefix_size = log_internal::FormatLogPrefix( entry_.log_severity(), entry_.timestamp(), entry_.tid(), entry_.source_basename(), entry_.source_line(), buf); - EXPECT_THAT(prefix_size, Eq(buf.data() - str.data())); + EXPECT_THAT(prefix_size, Eq(static_cast<size_t>(buf.data() - str.data()))); str.resize(prefix_size); return str; } @@ -207,10 +208,13 @@ TEST(LogEntryTest, EmptyFields) { } TEST(LogEntryTest, NegativeFields) { + // When Abseil's minimum C++ version is C++17, this conditional can be + // converted to a constexpr if and the static_cast below removed. if (std::is_signed<absl::LogEntry::tid_t>::value) { LogEntryTestPeer entry("foo.cc", -1234, kUsePrefix, absl::LogSeverity::kInfo, "2020-01-02T03:04:05.6789", - -451, "hello world"); + static_cast<absl::LogEntry::tid_t>(-451), + "hello world"); EXPECT_THAT(entry.FormatLogMessage(), Eq("I0102 03:04:05.678900 -451 foo.cc:-1234] hello world")); EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000), @@ -312,12 +316,15 @@ TEST(LogEntryTest, LongFields) { } TEST(LogEntryTest, LongNegativeFields) { + // When Abseil's minimum C++ version is C++17, this conditional can be + // converted to a constexpr if and the static_cast below removed. if (std::is_signed<absl::LogEntry::tid_t>::value) { LogEntryTestPeer entry( "I am the very model of a modern Major-General / " "I've information vegetable, animal, and mineral.", -2147483647, kUsePrefix, absl::LogSeverity::kInfo, - "2020-01-02T03:04:05.678967896789", -2147483647, + "2020-01-02T03:04:05.678967896789", + static_cast<absl::LogEntry::tid_t>(-2147483647), "I know the kings of England, and I quote the fights historical / " "From Marathon to Waterloo, in order categorical."); EXPECT_THAT( diff --git a/absl/log/log_format_test.cc b/absl/log/log_format_test.cc index 3fdb358a..397c8d0c 100644 --- a/absl/log/log_format_test.cc +++ b/absl/log/log_format_test.cc @@ -32,6 +32,7 @@ #include "absl/log/scoped_mock_log.h" #include "absl/strings/match.h" #include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" #include "absl/strings/string_view.h" namespace { @@ -108,7 +109,7 @@ TYPED_TEST(CharLogFormatTest, Printable) { TYPED_TEST(CharLogFormatTest, Unprintable) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); - const TypeParam value = 0xeeu; + constexpr auto value = static_cast<TypeParam>(0xeeu); auto comparison_stream = ComparisonStream(); comparison_stream << value; @@ -865,6 +866,111 @@ TEST(LogFormatTest, CustomNonCopyable) { LOG(INFO) << value; } +struct Point { + template <typename Sink> + friend void AbslStringify(Sink& sink, const Point& p) { + absl::Format(&sink, "(%d, %d)", p.x, p.y); + } + + int x = 10; + int y = 20; +}; + +TEST(LogFormatTest, AbslStringifyExample) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + Point p; + + EXPECT_CALL( + test_sink, + Send(AllOf( + TextMessage(Eq("(10, 20)")), TextMessage(Eq(absl::StrCat(p))), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "(10, 20)" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << p; +} + +struct PointWithAbslStringifiyAndOstream { + template <typename Sink> + friend void AbslStringify(Sink& sink, + const PointWithAbslStringifiyAndOstream& p) { + absl::Format(&sink, "(%d, %d)", p.x, p.y); + } + + int x = 10; + int y = 20; +}; + +ABSL_ATTRIBUTE_UNUSED std::ostream& operator<<( + std::ostream& os, const PointWithAbslStringifiyAndOstream&) { + return os << "Default to AbslStringify()"; +} + +TEST(LogFormatTest, CustomWithAbslStringifyAndOstream) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + PointWithAbslStringifiyAndOstream p; + + EXPECT_CALL( + test_sink, + Send(AllOf( + TextMessage(Eq("(10, 20)")), TextMessage(Eq(absl::StrCat(p))), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "(10, 20)" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << p; +} + +struct PointStreamsNothing { + template <typename Sink> + friend void AbslStringify(Sink&, const PointStreamsNothing&) {} + + int x = 10; + int y = 20; +}; + +TEST(LogFormatTest, AbslStringifyStreamsNothing) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + PointStreamsNothing p; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(Eq("77")), TextMessage(Eq(absl::StrCat(p, 77))), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "77" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << p << 77; +} + +struct PointMultipleAppend { + template <typename Sink> + friend void AbslStringify(Sink& sink, const PointMultipleAppend& p) { + sink.Append("("); + sink.Append(absl::StrCat(p.x, ", ", p.y, ")")); + } + + int x = 10; + int y = 20; +}; + +TEST(LogFormatTest, AbslStringifyMultipleAppend) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + PointMultipleAppend p; + + EXPECT_CALL( + test_sink, + Send(AllOf( + TextMessage(Eq("(10, 20)")), TextMessage(Eq(absl::StrCat(p))), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "(" } + value { str: "10, 20)" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << p; +} + TEST(ManipulatorLogFormatTest, BoolAlphaTrue) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); @@ -1501,6 +1607,31 @@ TEST(ManipulatorLogFormatTest, CustomClassStreamsNothing) { LOG(INFO) << value << 77; } +struct PointPercentV { + template <typename Sink> + friend void AbslStringify(Sink& sink, const PointPercentV& p) { + absl::Format(&sink, "(%v, %v)", p.x, p.y); + } + + int x = 10; + int y = 20; +}; + +TEST(ManipulatorLogFormatTest, IOManipsDoNotAffectAbslStringify) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + PointPercentV p; + + EXPECT_CALL( + test_sink, + Send(AllOf( + TextMessage(Eq("(10, 20)")), TextMessage(Eq(absl::StrCat(p))), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "(10, 20)" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::hex << p; +} + // Tests that verify the behavior when more data are streamed into a `LOG` // statement than fit in the buffer. // Structured logging scenario is tested in other unit tests since the output is diff --git a/absl/log/log_modifier_methods_test.cc b/absl/log/log_modifier_methods_test.cc index a9bf38b7..42e13b1b 100644 --- a/absl/log/log_modifier_methods_test.cc +++ b/absl/log/log_modifier_methods_test.cc @@ -130,7 +130,8 @@ TEST(TailCallsModifiesTest, WithTimestamp) { TEST(TailCallsModifiesTest, WithThreadID) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); - EXPECT_CALL(test_sink, Send(AllOf(ThreadID(Eq(1234))))); + EXPECT_CALL(test_sink, + Send(AllOf(ThreadID(Eq(absl::LogEntry::tid_t{1234}))))); test_sink.StartCapturingLogs(); LOG(INFO).WithThreadID(1234) << "hello world"; @@ -152,7 +153,8 @@ TEST(TailCallsModifiesTest, WithMetadataFrom) { Send(AllOf(SourceFilename(Eq("fake/file")), SourceBasename(Eq("file")), SourceLine(Eq(123)), Prefix(IsFalse()), LogSeverity(Eq(absl::LogSeverity::kWarning)), - Timestamp(Eq(absl::UnixEpoch())), ThreadID(Eq(456)), + Timestamp(Eq(absl::UnixEpoch())), + ThreadID(Eq(absl::LogEntry::tid_t{456})), TextMessage(Eq("forwarded: hello world")), Verbosity(Eq(7)), ENCODED_MESSAGE( EqualsProto(R"pb(value { literal: "forwarded: " } diff --git a/absl/log/scoped_mock_log_test.cc b/absl/log/scoped_mock_log_test.cc index 50689a0e..44b8d737 100644 --- a/absl/log/scoped_mock_log_test.cc +++ b/absl/log/scoped_mock_log_test.cc @@ -98,7 +98,7 @@ TEST(ScopedMockLogTest, LogMockCatchAndMatchSendExpectations) { log, Send(AllOf(SourceFilename(Eq("/my/very/very/very_long_source_file.cc")), SourceBasename(Eq("very_long_source_file.cc")), - SourceLine(Eq(777)), ThreadID(Eq(1234)), + SourceLine(Eq(777)), ThreadID(Eq(absl::LogEntry::tid_t{1234})), TextMessageWithPrefix(Truly([](absl::string_view msg) { return absl::EndsWith( msg, " very_long_source_file.cc:777] Info message"); diff --git a/absl/log/stripping_test.cc b/absl/log/stripping_test.cc index f37a0c55..83dfc2dc 100644 --- a/absl/log/stripping_test.cc +++ b/absl/log/stripping_test.cc @@ -215,7 +215,8 @@ class StrippingTest : public ::testing::Test { #elif defined(_WIN32) std::basic_string<TCHAR> path(4096, _T('\0')); while (true) { - const uint32_t ret = ::GetModuleFileName(nullptr, &path[0], path.size()); + const uint32_t ret = ::GetModuleFileName(nullptr, &path[0], + static_cast<DWORD>(path.size())); if (ret == 0) { absl::FPrintF( stderr, diff --git a/absl/memory/memory_test.cc b/absl/memory/memory_test.cc index 5d5719b3..8ac856b1 100644 --- a/absl/memory/memory_test.cc +++ b/absl/memory/memory_test.cc @@ -600,7 +600,7 @@ TEST(AllocatorTraits, FunctionsFull) { EXPECT_CALL(mock, allocate(13, &hint)).WillRepeatedly(Return(&y)); EXPECT_CALL(mock, construct(&x, &trace)); EXPECT_CALL(mock, destroy(&x)); - EXPECT_CALL(mock, max_size()).WillRepeatedly(Return(17)); + EXPECT_CALL(mock, max_size()).WillRepeatedly(Return(17u)); EXPECT_CALL(mock, select_on_container_copy_construction()) .WillRepeatedly(Return(FullMockAllocator(23))); @@ -613,7 +613,7 @@ TEST(AllocatorTraits, FunctionsFull) { Traits::destroy(mock, &x); EXPECT_EQ(1, trace); - EXPECT_EQ(17, Traits::max_size(mock)); + EXPECT_EQ(17u, Traits::max_size(mock)); EXPECT_EQ(0, mock.value); EXPECT_EQ(23, Traits::select_on_container_copy_construction(mock).value); diff --git a/absl/numeric/bits_benchmark.cc b/absl/numeric/bits_benchmark.cc index b9759583..719bfa81 100644 --- a/absl/numeric/bits_benchmark.cc +++ b/absl/numeric/bits_benchmark.cc @@ -25,17 +25,17 @@ namespace { template <typename T> static void BM_bitwidth(benchmark::State& state) { - const int count = state.range(0); + const auto count = static_cast<size_t>(state.range(0)); absl::BitGen rng; std::vector<T> values; values.reserve(count); - for (int i = 0; i < count; ++i) { + for (size_t i = 0; i < count; ++i) { values.push_back(absl::Uniform<T>(rng, 0, std::numeric_limits<T>::max())); } while (state.KeepRunningBatch(count)) { - for (int i = 0; i < count; ++i) { + for (size_t i = 0; i < count; ++i) { benchmark::DoNotOptimize(values[i]); } } @@ -47,17 +47,17 @@ BENCHMARK_TEMPLATE(BM_bitwidth, uint64_t)->Range(1, 1 << 20); template <typename T> static void BM_bitwidth_nonzero(benchmark::State& state) { - const int count = state.range(0); + const auto count = static_cast<size_t>(state.range(0)); absl::BitGen rng; std::vector<T> values; values.reserve(count); - for (int i = 0; i < count; ++i) { + for (size_t i = 0; i < count; ++i) { values.push_back(absl::Uniform<T>(rng, 1, std::numeric_limits<T>::max())); } while (state.KeepRunningBatch(count)) { - for (int i = 0; i < count; ++i) { + for (size_t i = 0; i < count; ++i) { const T value = values[i]; ABSL_ASSUME(value > 0); benchmark::DoNotOptimize(value); diff --git a/absl/random/CMakeLists.txt b/absl/random/CMakeLists.txt index d04c7081..c74fd300 100644 --- a/absl/random/CMakeLists.txt +++ b/absl/random/CMakeLists.txt @@ -569,7 +569,7 @@ absl_cc_library( ${ABSL_DEFAULT_COPTS} LINKOPTS ${ABSL_DEFAULT_LINKOPTS} - $<$<BOOL:${MINGW}>:"bcrypt"> + $<$<BOOL:${MINGW}>:-lbcrypt> DEPS absl::core_headers absl::optional diff --git a/absl/random/internal/fast_uniform_bits.h b/absl/random/internal/fast_uniform_bits.h index f3a5c00f..8d8ed045 100644 --- a/absl/random/internal/fast_uniform_bits.h +++ b/absl/random/internal/fast_uniform_bits.h @@ -151,7 +151,8 @@ FastUniformBits<UIntType>::Generate(URBG& g, // NOLINT(runtime/references) result_type r = static_cast<result_type>(g() - kMin); for (size_t n = 1; n < kIters; ++n) { - r = (r << kShift) + static_cast<result_type>(g() - kMin); + r = static_cast<result_type>(r << kShift) + + static_cast<result_type>(g() - kMin); } return r; } diff --git a/absl/random/internal/nonsecure_base.h b/absl/random/internal/nonsecure_base.h index c7d7fa4b..c3b80335 100644 --- a/absl/random/internal/nonsecure_base.h +++ b/absl/random/internal/nonsecure_base.h @@ -44,7 +44,7 @@ class RandenPoolSeedSeq { // Generate random unsigned values directly into the buffer. template <typename Contiguous> void generate_impl(ContiguousTag, Contiguous begin, Contiguous end) { - const size_t n = std::distance(begin, end); + const size_t n = static_cast<size_t>(std::distance(begin, end)); auto* a = &(*begin); RandenPool<uint8_t>::Fill( absl::MakeSpan(reinterpret_cast<uint8_t*>(a), sizeof(*a) * n)); diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel index ba7ae835..12a8d155 100644 --- a/absl/strings/BUILD.bazel +++ b/absl/strings/BUILD.bazel @@ -37,11 +37,14 @@ cc_library( "internal/charconv_bigint.h", "internal/charconv_parse.cc", "internal/charconv_parse.h", + "internal/damerau_levenshtein_distance.cc", "internal/memutil.cc", "internal/memutil.h", "internal/stl_type_traits.h", "internal/str_join_internal.h", "internal/str_split_internal.h", + "internal/stringify_sink.cc", + "internal/stringify_sink.h", "match.cc", "numbers.cc", "str_cat.cc", @@ -54,6 +57,8 @@ cc_library( "ascii.h", "charconv.h", "escaping.h", + "internal/damerau_levenshtein_distance.h", + "internal/has_absl_stringify.h", "internal/string_constant.h", "match.h", "numbers.h", @@ -179,6 +184,19 @@ cc_test( ) cc_test( + name = "damerau_levenshtein_distance_test", + size = "small", + srcs = [ + "internal/damerau_levenshtein_distance_test.cc", + ], + copts = ABSL_TEST_COPTS, + deps = [ + "//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( name = "memutil_benchmark", srcs = [ "internal/memutil.h", @@ -747,13 +765,13 @@ cc_test( ":cordz_test_helpers", ":str_format", ":strings", - "//absl/base", "//absl/base:config", "//absl/base:core_headers", "//absl/base:endian", "//absl/base:raw_logging_internal", "//absl/container:fixed_array", "//absl/hash", + "//absl/log", "//absl/random", "@com_google_googletest//:gtest_main", ], @@ -963,8 +981,8 @@ cc_test( copts = ABSL_TEST_COPTS, visibility = ["//visibility:private"], deps = [ + ":str_format", ":strings", - "//absl/base:core_headers", "@com_google_googletest//:gtest_main", ], ) @@ -1164,7 +1182,6 @@ cc_test( ":cord", ":str_format", ":strings", - "//absl/base:core_headers", "@com_google_googletest//:gtest_main", ], ) diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt index e1c2093a..7e91ebf2 100644 --- a/absl/strings/CMakeLists.txt +++ b/absl/strings/CMakeLists.txt @@ -21,7 +21,9 @@ absl_cc_library( "ascii.h" "charconv.h" "escaping.h" + "internal/damerau_levenshtein_distance.h" "internal/string_constant.h" + "internal/has_absl_stringify.h" "match.h" "numbers.h" "str_cat.h" @@ -39,8 +41,11 @@ absl_cc_library( "internal/charconv_bigint.h" "internal/charconv_parse.cc" "internal/charconv_parse.h" + "internal/damerau_levenshtein_distance.cc" "internal/memutil.cc" "internal/memutil.h" + "internal/stringify_sink.h" + "internal/stringify_sink.cc" "internal/stl_type_traits.h" "internal/str_join_internal.h" "internal/str_split_internal.h" @@ -134,6 +139,19 @@ absl_cc_test( absl_cc_test( NAME + damerau_levenshtein_distance_test + SRCS + "internal/damerau_levenshtein_distance_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::strings + absl::base + GTest::gmock_main +) + +absl_cc_test( + NAME memutil_test SRCS "internal/memutil.h" @@ -281,6 +299,7 @@ absl_cc_test( ${ABSL_TEST_COPTS} DEPS absl::strings + absl::str_format absl::core_headers GTest::gmock_main ) diff --git a/absl/strings/charconv.cc b/absl/strings/charconv.cc index 9b4bc5ea..69d420bc 100644 --- a/absl/strings/charconv.cc +++ b/absl/strings/charconv.cc @@ -298,7 +298,9 @@ struct CalculatedFloat { // minus the number of leading zero bits.) int BitWidth(uint128 value) { if (Uint128High64(value) == 0) { - return bit_width(Uint128Low64(value)); + // This static_cast is only needed when using a std::bit_width() + // implementation that does not have the fix for LWG 3656 applied. + return static_cast<int>(bit_width(Uint128Low64(value))); } return 128 - countl_zero(Uint128High64(value)); } @@ -339,14 +341,19 @@ template <typename FloatType> bool HandleEdgeCase(const strings_internal::ParsedFloat& input, bool negative, FloatType* value) { if (input.type == strings_internal::FloatType::kNan) { - // A bug in both clang and gcc would cause the compiler to optimize away the - // buffer we are building below. Declaring the buffer volatile avoids the - // issue, and has no measurable performance impact in microbenchmarks. + // A bug in both clang < 7 and gcc would cause the compiler to optimize + // away the buffer we are building below. Declaring the buffer volatile + // avoids the issue, and has no measurable performance impact in + // microbenchmarks. // // https://bugs.llvm.org/show_bug.cgi?id=37778 // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86113 constexpr ptrdiff_t kNanBufferSize = 128; +#if defined(__GNUC__) || (defined(__clang__) && __clang_major__ < 7) volatile char n_char_sequence[kNanBufferSize]; +#else + char n_char_sequence[kNanBufferSize]; +#endif if (input.subrange_begin == nullptr) { n_char_sequence[0] = '\0'; } else { @@ -575,7 +582,9 @@ CalculatedFloat CalculateFromParsedHexadecimal( const strings_internal::ParsedFloat& parsed_hex) { uint64_t mantissa = parsed_hex.mantissa; int exponent = parsed_hex.exponent; - int mantissa_width = bit_width(mantissa); + // This static_cast is only needed when using a std::bit_width() + // implementation that does not have the fix for LWG 3656 applied. + int mantissa_width = static_cast<int>(bit_width(mantissa)); const int shift = NormalizedShiftSize<FloatType>(mantissa_width, exponent); bool result_exact; exponent += shift; diff --git a/absl/strings/cord.cc b/absl/strings/cord.cc index 66f45fef..92822c05 100644 --- a/absl/strings/cord.cc +++ b/absl/strings/cord.cc @@ -420,6 +420,7 @@ Cord& Cord::operator=(absl::string_view src) { // we keep it here to make diffs easier. void Cord::InlineRep::AppendArray(absl::string_view src, MethodIdentifier method) { + MaybeRemoveEmptyCrcNode(); if (src.empty()) return; // memcpy(_, nullptr, 0) is undefined. size_t appended = 0; @@ -479,6 +480,10 @@ inline CordRep* Cord::TakeRep() && { template <typename C> inline void Cord::AppendImpl(C&& src) { auto constexpr method = CordzUpdateTracker::kAppendCord; + + contents_.MaybeRemoveEmptyCrcNode(); + if (src.empty()) return; + if (empty()) { // Since destination is empty, we can avoid allocating a node, if (src.contents_.is_tree()) { @@ -591,6 +596,9 @@ void Cord::Append(T&& src) { template void Cord::Append(std::string&& src); void Cord::Prepend(const Cord& src) { + contents_.MaybeRemoveEmptyCrcNode(); + if (src.empty()) return; + CordRep* src_tree = src.contents_.tree(); if (src_tree != nullptr) { CordRep::Ref(src_tree); @@ -605,7 +613,9 @@ void Cord::Prepend(const Cord& src) { } void Cord::PrependArray(absl::string_view src, MethodIdentifier method) { + contents_.MaybeRemoveEmptyCrcNode(); if (src.empty()) return; // memcpy(_, nullptr, 0) is undefined. + if (!contents_.is_tree()) { size_t cur_size = contents_.inline_size(); if (cur_size + src.size() <= InlineRep::kMaxInline) { @@ -665,6 +675,7 @@ void Cord::RemovePrefix(size_t n) { ABSL_INTERNAL_CHECK(n <= size(), absl::StrCat("Requested prefix size ", n, " exceeds Cord's size ", size())); + contents_.MaybeRemoveEmptyCrcNode(); CordRep* tree = contents_.tree(); if (tree == nullptr) { contents_.remove_prefix(n); @@ -695,6 +706,7 @@ void Cord::RemoveSuffix(size_t n) { ABSL_INTERNAL_CHECK(n <= size(), absl::StrCat("Requested suffix size ", n, " exceeds Cord's size ", size())); + contents_.MaybeRemoveEmptyCrcNode(); CordRep* tree = contents_.tree(); if (tree == nullptr) { contents_.reduce_size(n); @@ -844,9 +856,11 @@ inline absl::string_view Cord::InlineRep::FindFlatStartPiece() const { void Cord::SetExpectedChecksum(uint32_t crc) { auto constexpr method = CordzUpdateTracker::kSetExpectedChecksum; - if (empty()) return; - - if (!contents_.is_tree()) { + if (empty()) { + contents_.MaybeRemoveEmptyCrcNode(); + CordRep* rep = CordRepCrc::New(nullptr, crc); + contents_.EmplaceTree(rep, method); + } else if (!contents_.is_tree()) { CordRep* rep = contents_.MakeFlatWithExtraCapacity(0); rep = CordRepCrc::New(rep, crc); contents_.EmplaceTree(rep, method); @@ -929,6 +943,7 @@ inline int Cord::CompareSlowPath(const Cord& rhs, size_t compared_size, } inline absl::string_view Cord::GetFirstChunk(const Cord& c) { + if (c.empty()) return {}; return c.contents_.FindFlatStartPiece(); } inline absl::string_view Cord::GetFirstChunk(absl::string_view sv) { @@ -1166,6 +1181,10 @@ absl::string_view Cord::FlattenSlowPath() { /* static */ bool Cord::GetFlatAux(CordRep* rep, absl::string_view* fragment) { assert(rep != nullptr); + if (rep->length == 0) { + *fragment = absl::string_view(); + return true; + } rep = cord_internal::SkipCrcNode(rep); if (rep->IsFlat()) { *fragment = absl::string_view(rep->flat()->Data(), rep->length); @@ -1197,6 +1216,7 @@ absl::string_view Cord::FlattenSlowPath() { absl::cord_internal::CordRep* rep, absl::FunctionRef<void(absl::string_view)> callback) { assert(rep != nullptr); + if (rep->length == 0) return; rep = cord_internal::SkipCrcNode(rep); if (rep->IsBtree()) { @@ -1230,7 +1250,11 @@ static void DumpNode(CordRep* rep, bool include_data, std::ostream* os, if (include_data) *os << static_cast<void*>(rep); *os << "]"; *os << " " << std::setw(indent) << ""; - if (rep->IsCrc()) { + bool leaf = false; + if (rep == nullptr) { + *os << "NULL\n"; + leaf = true; + } else if (rep->IsCrc()) { *os << "CRC crc=" << rep->crc()->crc << "\n"; indent += kIndentStep; rep = rep->crc()->child; @@ -1239,6 +1263,7 @@ static void DumpNode(CordRep* rep, bool include_data, std::ostream* os, indent += kIndentStep; rep = rep->substring()->child; } else { // Leaf or ring + leaf = true; if (rep->IsExternal()) { *os << "EXTERNAL ["; if (include_data) @@ -1252,6 +1277,8 @@ static void DumpNode(CordRep* rep, bool include_data, std::ostream* os, } else { CordRepBtree::Dump(rep, /*label=*/ "", include_data, *os); } + } + if (leaf) { if (stack.empty()) break; rep = stack.back(); stack.pop_back(); @@ -1297,11 +1324,14 @@ static bool VerifyNode(CordRep* root, CordRep* start_node, node->substring()->child->length, ReportError(root, node)); } else if (node->IsCrc()) { - ABSL_INTERNAL_CHECK(node->crc()->child != nullptr, - ReportError(root, node)); - ABSL_INTERNAL_CHECK(node->crc()->length == node->crc()->child->length, - ReportError(root, node)); - worklist.push_back(node->crc()->child); + ABSL_INTERNAL_CHECK( + node->crc()->child != nullptr || node->crc()->length == 0, + ReportError(root, node)); + if (node->crc()->child != nullptr) { + ABSL_INTERNAL_CHECK(node->crc()->length == node->crc()->child->length, + ReportError(root, node)); + worklist.push_back(node->crc()->child); + } } } while (!worklist.empty()); return true; diff --git a/absl/strings/cord.h b/absl/strings/cord.h index 88e1c85d..6e3da89e 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -926,6 +926,13 @@ class Cord { void set_inline_size(size_t size) { data_.set_inline_size(size); } size_t inline_size() const { return data_.inline_size(); } + // Empty cords that carry a checksum have a CordRepCrc node with a null + // child node. The code can avoid lots of special cases where it would + // otherwise transition from tree to inline storage if we just remove the + // CordRepCrc node before mutations. Must never be called inside a + // CordzUpdateScope since it untracks the cordz info. + void MaybeRemoveEmptyCrcNode(); + cord_internal::InlineData data_; }; InlineRep contents_; @@ -1236,6 +1243,18 @@ inline void Cord::InlineRep::CopyToArray(char* dst) const { cord_internal::SmallMemmove(dst, data_.as_chars(), n); } +inline void Cord::InlineRep::MaybeRemoveEmptyCrcNode() { + CordRep* rep = tree(); + if (rep == nullptr || ABSL_PREDICT_TRUE(rep->length > 0)) { + return; + } + assert(rep->IsCrc()); + assert(rep->crc()->child == nullptr); + CordzInfo::MaybeUntrackCord(cordz_info()); + CordRep::Unref(rep); + ResetToEmpty(); +} + constexpr inline Cord::Cord() noexcept {} inline Cord::Cord(absl::string_view src) @@ -1285,7 +1304,7 @@ inline size_t Cord::size() const { return contents_.size(); } -inline bool Cord::empty() const { return contents_.empty(); } +inline bool Cord::empty() const { return size() == 0; } inline size_t Cord::EstimatedMemoryUsage( CordMemoryAccounting accounting_method) const { @@ -1411,7 +1430,11 @@ inline Cord::ChunkIterator::ChunkIterator(cord_internal::CordRep* tree) { inline Cord::ChunkIterator::ChunkIterator(const Cord* cord) { if (CordRep* tree = cord->contents_.tree()) { bytes_remaining_ = tree->length; - InitTree(tree); + if (ABSL_PREDICT_TRUE(bytes_remaining_ != 0)) { + InitTree(tree); + } else { + current_chunk_ = {}; + } } else { bytes_remaining_ = cord->contents_.inline_size(); current_chunk_ = {cord->contents_.data(), bytes_remaining_}; @@ -1580,7 +1603,7 @@ inline void Cord::ForEachChunk( if (rep == nullptr) { callback(absl::string_view(contents_.data(), contents_.size())); } else { - return ForEachChunkAux(rep, callback); + ForEachChunkAux(rep, callback); } } diff --git a/absl/strings/cord_test.cc b/absl/strings/cord_test.cc index d28ba113..a4fa8955 100644 --- a/absl/strings/cord_test.cc +++ b/absl/strings/cord_test.cc @@ -28,7 +28,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "absl/base/casts.h" #include "absl/base/config.h" #include "absl/base/internal/endian.h" #include "absl/base/internal/raw_logging.h" @@ -1989,6 +1988,12 @@ TEST_P(CordTest, HugeCord) { // Tests that Append() works ok when handed a self reference TEST_P(CordTest, AppendSelf) { + // Test the empty case. + absl::Cord empty; + MaybeHarden(empty); + empty.Append(empty); + ASSERT_EQ(empty, ""); + // We run the test until data is ~16K // This guarantees it covers small, medium and large data. std::string control_data = "Abc"; @@ -2713,7 +2718,7 @@ class CordMutator { // clang-format off // This array is constant-initialized in conformant compilers. -CordMutator cord_mutators[] ={ +CordMutator cord_mutators[] = { {"clear", [](absl::Cord& c) { c.Clear(); }}, {"overwrite", [](absl::Cord& c) { c = "overwritten"; }}, { @@ -2743,6 +2748,25 @@ CordMutator cord_mutators[] ={ [](absl::Cord& c) { c.RemoveSuffix(c.size() / 2); } }, { + "append empty string", + [](absl::Cord& c) { c.Append(""); }, + [](absl::Cord& c) { } + }, + { + "append empty cord", + [](absl::Cord& c) { c.Append(absl::Cord()); }, + [](absl::Cord& c) { } + }, + { + "append empty checksummed cord", + [](absl::Cord& c) { + absl::Cord to_append; + to_append.SetExpectedChecksum(999); + c.Append(to_append); + }, + [](absl::Cord& c) { } + }, + { "prepend string", [](absl::Cord& c) { c.Prepend("9876543210"); }, [](absl::Cord& c) { c.RemovePrefix(10); } @@ -2764,12 +2788,33 @@ CordMutator cord_mutators[] ={ [](absl::Cord& c) { c.RemovePrefix(10); } }, { + "prepend empty string", + [](absl::Cord& c) { c.Prepend(""); }, + [](absl::Cord& c) { } + }, + { + "prepend empty cord", + [](absl::Cord& c) { c.Prepend(absl::Cord()); }, + [](absl::Cord& c) { } + }, + { + "prepend empty checksummed cord", + [](absl::Cord& c) { + absl::Cord to_prepend; + to_prepend.SetExpectedChecksum(999); + c.Prepend(to_prepend); + }, + [](absl::Cord& c) { } + }, + { "prepend self", [](absl::Cord& c) { c.Prepend(c); }, [](absl::Cord& c) { c.RemovePrefix(c.size() / 2); } }, - {"remove prefix", [](absl::Cord& c) { c.RemovePrefix(2); }}, - {"remove suffix", [](absl::Cord& c) { c.RemoveSuffix(2); }}, + {"remove prefix", [](absl::Cord& c) { c.RemovePrefix(c.size() / 2); }}, + {"remove suffix", [](absl::Cord& c) { c.RemoveSuffix(c.size() / 2); }}, + {"remove 0-prefix", [](absl::Cord& c) { c.RemovePrefix(0); }}, + {"remove 0-suffix", [](absl::Cord& c) { c.RemoveSuffix(0); }}, {"subcord", [](absl::Cord& c) { c = c.Subcord(1, c.size() - 2); }}, { "swap inline", @@ -2811,6 +2856,12 @@ TEST_P(CordTest, ExpectedChecksum) { EXPECT_EQ(c1.ExpectedChecksum().value_or(0), 12345); EXPECT_EQ(c1, base_value); + // Test that setting an expected checksum again doesn't crash or leak + // memory. + c1.SetExpectedChecksum(12345); + EXPECT_EQ(c1.ExpectedChecksum().value_or(0), 12345); + EXPECT_EQ(c1, base_value); + // CRC persists through copies, assignments, and moves: absl::Cord c1_copy_construct = c1; EXPECT_EQ(c1_copy_construct.ExpectedChecksum().value_or(0), 12345); @@ -2835,6 +2886,13 @@ TEST_P(CordTest, ExpectedChecksum) { c2.SetExpectedChecksum(24680); mutator.Mutate(c2); + + if (c1 == c2) { + // Not a mutation (for example, appending the empty string). + // Whether the checksum is removed is not defined. + continue; + } + EXPECT_EQ(c2.ExpectedChecksum(), absl::nullopt); if (mutator.CanUndo()) { @@ -2904,3 +2962,98 @@ TEST_P(CordTest, ExpectedChecksum) { } } } + +// Test the special cases encountered with an empty checksummed cord. +TEST_P(CordTest, ChecksummedEmptyCord) { + absl::Cord c1; + EXPECT_FALSE(c1.ExpectedChecksum().has_value()); + + // Setting an expected checksum works. + c1.SetExpectedChecksum(12345); + EXPECT_EQ(c1.ExpectedChecksum().value_or(0), 12345); + EXPECT_EQ(c1, ""); + EXPECT_TRUE(c1.empty()); + + // Test that setting an expected checksum again doesn't crash or leak memory. + c1.SetExpectedChecksum(12345); + EXPECT_EQ(c1.ExpectedChecksum().value_or(0), 12345); + EXPECT_EQ(c1, ""); + EXPECT_TRUE(c1.empty()); + + // CRC persists through copies, assignments, and moves: + absl::Cord c1_copy_construct = c1; + EXPECT_EQ(c1_copy_construct.ExpectedChecksum().value_or(0), 12345); + + absl::Cord c1_copy_assign; + c1_copy_assign = c1; + EXPECT_EQ(c1_copy_assign.ExpectedChecksum().value_or(0), 12345); + + absl::Cord c1_move(std::move(c1_copy_assign)); + EXPECT_EQ(c1_move.ExpectedChecksum().value_or(0), 12345); + + EXPECT_EQ(c1.ExpectedChecksum().value_or(0), 12345); + + // A CRC Cord compares equal to its non-CRC value. + EXPECT_EQ(c1, absl::Cord()); + + for (const CordMutator& mutator : cord_mutators) { + SCOPED_TRACE(mutator.Name()); + + // Exercise mutating an empty checksummed cord to catch crashes and exercise + // memory sanitizers. + absl::Cord c2; + c2.SetExpectedChecksum(24680); + mutator.Mutate(c2); + + if (c2.empty()) { + // Not a mutation + continue; + } + EXPECT_EQ(c2.ExpectedChecksum(), absl::nullopt); + + if (mutator.CanUndo()) { + mutator.Undo(c2); + } + } + + absl::Cord c3; + c3.SetExpectedChecksum(999); + const absl::Cord& cc3 = c3; + + // Test that all cord reading operations function in the face of an + // expected checksum. + EXPECT_TRUE(cc3.StartsWith("")); + EXPECT_TRUE(cc3.EndsWith("")); + EXPECT_TRUE(cc3.empty()); + EXPECT_EQ(cc3, ""); + EXPECT_EQ(cc3, absl::Cord()); + EXPECT_EQ(cc3.size(), 0); + EXPECT_EQ(cc3.Compare(absl::Cord()), 0); + EXPECT_EQ(cc3.Compare(c1), 0); + EXPECT_EQ(cc3.Compare(cc3), 0); + EXPECT_EQ(cc3.Compare(""), 0); + EXPECT_EQ(cc3.Compare("wxyz"), -1); + EXPECT_EQ(cc3.Compare(absl::Cord("wxyz")), -1); + EXPECT_EQ(absl::Cord("wxyz").Compare(cc3), 1); + EXPECT_EQ(std::string(cc3), ""); + + std::string dest; + absl::CopyCordToString(cc3, &dest); + EXPECT_EQ(dest, ""); + + for (absl::string_view chunk : cc3.Chunks()) { // NOLINT(unreachable loop) + static_cast<void>(chunk); + GTEST_FAIL() << "no chunks expected"; + } + EXPECT_TRUE(cc3.chunk_begin() == cc3.chunk_end()); + + for (char ch : cc3.Chars()) { // NOLINT(unreachable loop) + static_cast<void>(ch); + GTEST_FAIL() << "no chars expected"; + } + EXPECT_TRUE(cc3.char_begin() == cc3.char_end()); + + EXPECT_EQ(cc3.TryFlat(), ""); + EXPECT_EQ(absl::HashOf(c3), absl::HashOf(absl::Cord())); + EXPECT_EQ(absl::HashOf(c3), absl::HashOf(absl::string_view())); +} diff --git a/absl/strings/escaping.h b/absl/strings/escaping.h index f5ca26c5..aa6d1750 100644 --- a/absl/strings/escaping.h +++ b/absl/strings/escaping.h @@ -122,6 +122,8 @@ std::string Utf8SafeCHexEscape(absl::string_view src); // Converts a `src` string encoded in Base64 to its binary equivalent, writing // it to a `dest` buffer, returning `true` on success. If `src` contains invalid // characters, `dest` is cleared and returns `false`. +// Padding is optional. If padding is included, it must be correct. In the +// padding, '=' and '.' are treated identically. bool Base64Unescape(absl::string_view src, std::string* dest); // WebSafeBase64Unescape() @@ -129,6 +131,8 @@ bool Base64Unescape(absl::string_view src, std::string* dest); // Converts a `src` string encoded in Base64 to its binary equivalent, writing // it to a `dest` buffer, but using '-' instead of '+', and '_' instead of '/'. // If `src` contains invalid characters, `dest` is cleared and returns `false`. +// Padding is optional. If padding is included, it must be correct. In the +// padding, '=' and '.' are treated identically. bool WebSafeBase64Unescape(absl::string_view src, std::string* dest); // Base64Escape() diff --git a/absl/strings/escaping_test.cc b/absl/strings/escaping_test.cc index 45671a0e..44ffcba7 100644 --- a/absl/strings/escaping_test.cc +++ b/absl/strings/escaping_test.cc @@ -617,6 +617,48 @@ TEST(Base64, EscapeAndUnescape) { TestEscapeAndUnescape<std::string>(); } +TEST(Base64, Padding) { + // Padding is optional. + // '.' is an acceptable padding character, just like '='. + std::initializer_list<absl::string_view> good_padding = { + "YQ", + "YQ==", + "YQ=.", + "YQ.=", + "YQ..", + }; + for (absl::string_view b64 : good_padding) { + std::string decoded; + EXPECT_TRUE(absl::Base64Unescape(b64, &decoded)); + EXPECT_EQ(decoded, "a"); + std::string websafe_decoded; + EXPECT_TRUE(absl::WebSafeBase64Unescape(b64, &websafe_decoded)); + EXPECT_EQ(websafe_decoded, "a"); + } + std::initializer_list<absl::string_view> bad_padding = { + "YQ=", + "YQ.", + "YQ===", + "YQ==.", + "YQ=.=", + "YQ=..", + "YQ.==", + "YQ.=.", + "YQ..=", + "YQ...", + "YQ====", + "YQ....", + "YQ=====", + "YQ.....", + }; + for (absl::string_view b64 : bad_padding) { + std::string decoded; + EXPECT_FALSE(absl::Base64Unescape(b64, &decoded)); + std::string websafe_decoded; + EXPECT_FALSE(absl::WebSafeBase64Unescape(b64, &websafe_decoded)); + } +} + TEST(Base64, DISABLED_HugeData) { const size_t kSize = size_t(3) * 1000 * 1000 * 1000; static_assert(kSize % 3 == 0, "kSize must be divisible by 3"); diff --git a/absl/strings/internal/cord_internal.h b/absl/strings/internal/cord_internal.h index f32fd416..fcca3a28 100644 --- a/absl/strings/internal/cord_internal.h +++ b/absl/strings/internal/cord_internal.h @@ -423,8 +423,8 @@ constexpr char GetOrNull(absl::string_view data, size_t pos) { return pos < data.size() ? data[pos] : '\0'; } -// We store cordz_info as 64 bit pointer value in big endian format. This -// guarantees that the least significant byte of cordz_info matches the last +// We store cordz_info as 64 bit pointer value in little endian format. This +// guarantees that the least significant byte of cordz_info matches the first // byte of the inline data representation in as_chars_, which holds the inlined // size or the 'is_tree' bit. using cordz_info_t = int64_t; @@ -434,14 +434,14 @@ using cordz_info_t = int64_t; static_assert(sizeof(cordz_info_t) * 2 == kMaxInline + 1, ""); static_assert(sizeof(cordz_info_t) >= sizeof(intptr_t), ""); -// BigEndianByte() creates a big endian representation of 'value', i.e.: a big -// endian value where the last byte in the host's representation holds 'value`, -// with all other bytes being 0. -static constexpr cordz_info_t BigEndianByte(unsigned char value) { +// LittleEndianByte() creates a little endian representation of 'value', i.e.: +// a little endian value where the first byte in the host's representation +// holds 'value`, with all other bytes being 0. +static constexpr cordz_info_t LittleEndianByte(unsigned char value) { #if defined(ABSL_IS_BIG_ENDIAN) - return value; -#else return static_cast<cordz_info_t>(value) << ((sizeof(cordz_info_t) - 1) * 8); +#else + return value; #endif } @@ -450,25 +450,37 @@ class InlineData { // DefaultInitType forces the use of the default initialization constructor. enum DefaultInitType { kDefaultInit }; - // kNullCordzInfo holds the big endian representation of intptr_t(1) + // kNullCordzInfo holds the little endian representation of intptr_t(1) // This is the 'null' / initial value of 'cordz_info'. The null value // is specifically big endian 1 as with 64-bit pointers, the last // byte of cordz_info overlaps with the last byte holding the tag. - static constexpr cordz_info_t kNullCordzInfo = BigEndianByte(1); + static constexpr cordz_info_t kNullCordzInfo = LittleEndianByte(1); + + // kTagOffset contains the offset of the control byte / tag. This constant is + // intended mostly for debugging purposes: do not remove this constant as it + // is actively inspected and used by gdb pretty printing code. + static constexpr size_t kTagOffset = 0; constexpr InlineData() : as_chars_{0} {} explicit InlineData(DefaultInitType) {} explicit constexpr InlineData(CordRep* rep) : as_tree_(rep) {} explicit constexpr InlineData(absl::string_view chars) - : as_chars_{ - GetOrNull(chars, 0), GetOrNull(chars, 1), - GetOrNull(chars, 2), GetOrNull(chars, 3), - GetOrNull(chars, 4), GetOrNull(chars, 5), - GetOrNull(chars, 6), GetOrNull(chars, 7), - GetOrNull(chars, 8), GetOrNull(chars, 9), - GetOrNull(chars, 10), GetOrNull(chars, 11), - GetOrNull(chars, 12), GetOrNull(chars, 13), - GetOrNull(chars, 14), static_cast<char>((chars.size() << 1))} {} + : as_chars_{static_cast<char>((chars.size() << 1)), + GetOrNull(chars, 0), + GetOrNull(chars, 1), + GetOrNull(chars, 2), + GetOrNull(chars, 3), + GetOrNull(chars, 4), + GetOrNull(chars, 5), + GetOrNull(chars, 6), + GetOrNull(chars, 7), + GetOrNull(chars, 8), + GetOrNull(chars, 9), + GetOrNull(chars, 10), + GetOrNull(chars, 11), + GetOrNull(chars, 12), + GetOrNull(chars, 13), + GetOrNull(chars, 14)} {} // Returns true if the current instance is empty. // The 'empty value' is an inlined data value of zero length. @@ -499,8 +511,8 @@ class InlineData { // Requires the current instance to hold a tree value. CordzInfo* cordz_info() const { assert(is_tree()); - intptr_t info = static_cast<intptr_t>( - absl::big_endian::ToHost64(static_cast<uint64_t>(as_tree_.cordz_info))); + intptr_t info = static_cast<intptr_t>(absl::little_endian::ToHost64( + static_cast<uint64_t>(as_tree_.cordz_info))); assert(info & 1); return reinterpret_cast<CordzInfo*>(info - 1); } @@ -512,7 +524,7 @@ class InlineData { assert(is_tree()); uintptr_t info = reinterpret_cast<uintptr_t>(cordz_info) | 1; as_tree_.cordz_info = - static_cast<cordz_info_t>(absl::big_endian::FromHost64(info)); + static_cast<cordz_info_t>(absl::little_endian::FromHost64(info)); } // Resets the current cordz_info to null / empty. @@ -525,7 +537,7 @@ class InlineData { // Requires the current instance to hold inline data. const char* as_chars() const { assert(!is_tree()); - return as_chars_; + return &as_chars_[1]; } // Returns a mutable pointer to the character data inside this instance. @@ -543,7 +555,7 @@ class InlineData { // // It's an error to read from the returned pointer without a preceding write // if the current instance does not hold inline data, i.e.: is_tree() == true. - char* as_chars() { return as_chars_; } + char* as_chars() { return &as_chars_[1]; } // Returns the tree value of this value. // Requires the current instance to hold a tree value. @@ -579,7 +591,7 @@ class InlineData { // See the documentation on 'as_chars()' for more information and examples. void set_inline_size(size_t size) { ABSL_ASSERT(size <= kMaxInline); - tag() = static_cast<char>(size << 1); + tag() = static_cast<int8_t>(size << 1); } // Compares 'this' inlined data with rhs. The comparison is a straightforward @@ -608,20 +620,13 @@ class InlineData { private: // See cordz_info_t for forced alignment and size of `cordz_info` details. struct AsTree { - explicit constexpr AsTree(absl::cord_internal::CordRep* tree) - : rep(tree), cordz_info(kNullCordzInfo) {} - // This union uses up extra space so that whether rep is 32 or 64 bits, - // cordz_info will still start at the eighth byte, and the last - // byte of cordz_info will still be the last byte of InlineData. - union { - absl::cord_internal::CordRep* rep; - cordz_info_t unused_aligner; - }; - cordz_info_t cordz_info; + explicit constexpr AsTree(absl::cord_internal::CordRep* tree) : rep(tree) {} + cordz_info_t cordz_info = kNullCordzInfo; + absl::cord_internal::CordRep* rep; }; - char& tag() { return reinterpret_cast<char*>(this)[kMaxInline]; } - char tag() const { return reinterpret_cast<const char*>(this)[kMaxInline]; } + int8_t& tag() { return reinterpret_cast<int8_t*>(this)[0]; } + int8_t tag() const { return reinterpret_cast<const int8_t*>(this)[0]; } // If the data has length <= kMaxInline, we store it in `as_chars_`, and // store the size in the last char of `as_chars_` shifted left + 1. diff --git a/absl/strings/internal/cord_rep_crc.cc b/absl/strings/internal/cord_rep_crc.cc index ee140354..7d7273ef 100644 --- a/absl/strings/internal/cord_rep_crc.cc +++ b/absl/strings/internal/cord_rep_crc.cc @@ -25,8 +25,7 @@ ABSL_NAMESPACE_BEGIN namespace cord_internal { CordRepCrc* CordRepCrc::New(CordRep* child, uint32_t crc) { - assert(child != nullptr); - if (child->IsCrc()) { + if (child != nullptr && child->IsCrc()) { if (child->refcount.IsOne()) { child->crc()->crc = crc; return child->crc(); @@ -37,7 +36,7 @@ CordRepCrc* CordRepCrc::New(CordRep* child, uint32_t crc) { CordRep::Unref(old); } auto* new_cordrep = new CordRepCrc; - new_cordrep->length = child->length; + new_cordrep->length = child != nullptr ? child->length : 0; new_cordrep->tag = cord_internal::CRC; new_cordrep->child = child; new_cordrep->crc = crc; @@ -45,7 +44,9 @@ CordRepCrc* CordRepCrc::New(CordRep* child, uint32_t crc) { } void CordRepCrc::Destroy(CordRepCrc* node) { - CordRep::Unref(node->child); + if (node->child != nullptr) { + CordRep::Unref(node->child); + } delete node; } diff --git a/absl/strings/internal/cord_rep_crc.h b/absl/strings/internal/cord_rep_crc.h index 5294b0d1..455a1127 100644 --- a/absl/strings/internal/cord_rep_crc.h +++ b/absl/strings/internal/cord_rep_crc.h @@ -40,7 +40,7 @@ struct CordRepCrc : public CordRep { // If the specified `child` is itself a CordRepCrc node, then this method // either replaces the existing node, or directly updates the crc value in it // depending on the node being shared or not, i.e.: refcount.IsOne(). - // `child` must not be null. Never returns null. + // `child` must only be null if the Cord is empty. Never returns null. static CordRepCrc* New(CordRep* child, uint32_t crc); // Destroys (deletes) the provided node. `node` must not be null. diff --git a/absl/strings/internal/cord_rep_crc_test.cc b/absl/strings/internal/cord_rep_crc_test.cc index d73ea7b3..42a9110b 100644 --- a/absl/strings/internal/cord_rep_crc_test.cc +++ b/absl/strings/internal/cord_rep_crc_test.cc @@ -27,14 +27,11 @@ namespace { using ::absl::cordrep_testing::MakeFlat; using ::testing::Eq; +using ::testing::IsNull; using ::testing::Ne; #if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST -TEST(CordRepCrc, NewWithNullPtr) { - EXPECT_DEATH(CordRepCrc::New(nullptr, 0), ""); -} - TEST(CordRepCrc, RemoveCrcWithNullptr) { EXPECT_DEATH(RemoveCrcNode(nullptr), ""); } @@ -82,6 +79,16 @@ TEST(CordRepCrc, NewExistingCrcShared) { CordRep::Unref(new_crc); } +TEST(CordRepCrc, NewEmpty) { + CordRepCrc* crc = CordRepCrc::New(nullptr, 12345); + EXPECT_TRUE(crc->refcount.IsOne()); + EXPECT_THAT(crc->child, IsNull()); + EXPECT_THAT(crc->length, Eq(0u)); + EXPECT_THAT(crc->crc, Eq(12345u)); + EXPECT_TRUE(crc->refcount.IsOne()); + CordRepCrc::Destroy(crc); +} + TEST(CordRepCrc, RemoveCrcNotCrc) { CordRep* rep = cordrep_testing::MakeFlat("Hello world"); CordRep* nocrc = RemoveCrcNode(rep); diff --git a/absl/strings/internal/damerau_levenshtein_distance.cc b/absl/strings/internal/damerau_levenshtein_distance.cc new file mode 100644 index 00000000..a084568f --- /dev/null +++ b/absl/strings/internal/damerau_levenshtein_distance.cc @@ -0,0 +1,93 @@ +// Copyright 2022 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/strings/internal/damerau_levenshtein_distance.h" + +#include <algorithm> +#include <array> +#include <numeric> + +#include "absl/strings/string_view.h" +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace strings_internal { +// Calculate DamerauLevenshtein (adjacent transpositions) distance +// between two strings, +// https://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance. The +// algorithm follows the condition that no substring is edited more than once. +// While this can reduce is larger distance, it's a) a much simpler algorithm +// and b) more realistic for the case that typographic mistakes should be +// detected. +// When the distance is larger than cutoff, or one of the strings has more +// than MAX_SIZE=100 characters, the code returns min(MAX_SIZE, cutoff) + 1. +uint8_t CappedDamerauLevenshteinDistance(absl::string_view s1, + absl::string_view s2, uint8_t cutoff) { + const uint8_t MAX_SIZE = 100; + const uint8_t _cutoff = std::min(MAX_SIZE, cutoff); + const uint8_t cutoff_plus_1 = static_cast<uint8_t>(_cutoff + 1); + + if (s1.size() > s2.size()) std::swap(s1, s2); + if (s1.size() + _cutoff < s2.size() || s2.size() > MAX_SIZE) + return cutoff_plus_1; + + if (s1.empty()) + return static_cast<uint8_t>(s2.size()); + + // Lower diagonal bound: y = x - lower_diag + const uint8_t lower_diag = + _cutoff - static_cast<uint8_t>(s2.size() - s1.size()); + // Upper diagonal bound: y = x + upper_diag + const uint8_t upper_diag = _cutoff; + + // d[i][j] is the number of edits required to convert s1[0, i] to s2[0, j] + std::array<std::array<uint8_t, MAX_SIZE + 2>, MAX_SIZE + 2> d; + std::iota(d[0].begin(), d[0].begin() + upper_diag + 1, 0); + d[0][cutoff_plus_1] = cutoff_plus_1; + for (size_t i = 1; i <= s1.size(); ++i) { + // Deduce begin of relevant window. + size_t j_begin = 1; + if (i > lower_diag) { + j_begin = i - lower_diag; + d[i][j_begin - 1] = cutoff_plus_1; + } else { + d[i][0] = static_cast<uint8_t>(i); + } + + // Deduce end of relevant window. + size_t j_end = i + upper_diag; + if (j_end > s2.size()) { + j_end = s2.size(); + } else { + d[i][j_end + 1] = cutoff_plus_1; + } + + for (size_t j = j_begin; j <= j_end; ++j) { + const uint8_t deletion_distance = d[i - 1][j] + 1; + const uint8_t insertion_distance = d[i][j - 1] + 1; + const uint8_t mismatched_tail_cost = s1[i - 1] == s2[j - 1] ? 0 : 1; + const uint8_t mismatch_distance = d[i - 1][j - 1] + mismatched_tail_cost; + uint8_t transposition_distance = _cutoff + 1; + if (i > 1 && j > 1 && s1[i - 1] == s2[j - 2] && s1[i - 2] == s2[j - 1]) + transposition_distance = d[i - 2][j - 2] + 1; + d[i][j] = std::min({cutoff_plus_1, deletion_distance, insertion_distance, + mismatch_distance, transposition_distance}); + } + } + return d[s1.size()][s2.size()]; +} + +} // namespace strings_internal + +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/strings/internal/damerau_levenshtein_distance.h b/absl/strings/internal/damerau_levenshtein_distance.h new file mode 100644 index 00000000..1a968425 --- /dev/null +++ b/absl/strings/internal/damerau_levenshtein_distance.h @@ -0,0 +1,35 @@ +// Copyright 2022 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ABSL_STRINGS_INTERNAL_DAMERAU_LEVENSHTEIN_DISTANCE_H_ +#define ABSL_STRINGS_INTERNAL_DAMERAU_LEVENSHTEIN_DISTANCE_H_ + +#include <numeric> +#include <vector> + +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace strings_internal { +// Calculate DamerauLevenshtein distance between two strings. +// When the distance is larger than cutoff, the code just returns cutoff + 1. +uint8_t CappedDamerauLevenshteinDistance(absl::string_view s1, + absl::string_view s2, uint8_t cutoff); + +} // namespace strings_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_STRINGS_INTERNAL_DAMERAU_LEVENSHTEIN_DISTANCE_H_ diff --git a/absl/strings/internal/damerau_levenshtein_distance_test.cc b/absl/strings/internal/damerau_levenshtein_distance_test.cc new file mode 100644 index 00000000..a342b7db --- /dev/null +++ b/absl/strings/internal/damerau_levenshtein_distance_test.cc @@ -0,0 +1,99 @@ +// Copyright 2022 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/strings/internal/damerau_levenshtein_distance.h" + +#include <cstdint> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace { + +using absl::strings_internal::CappedDamerauLevenshteinDistance; + +TEST(Distance, TestDistances) { + EXPECT_THAT(CappedDamerauLevenshteinDistance("ab", "ab", 6), uint8_t{0}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("a", "b", 6), uint8_t{1}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("ca", "abc", 6), uint8_t{3}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("abcd", "ad", 6), uint8_t{2}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("abcd", "cadb", 6), uint8_t{4}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("abcd", "bdac", 6), uint8_t{4}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("ab", "ab", 0), uint8_t{0}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("", "", 0), uint8_t{0}); + // combinations for 3-character strings: + // 1, 2, 3 removals, insertions or replacements and transpositions + EXPECT_THAT(CappedDamerauLevenshteinDistance("abc", "abc", 6), uint8_t{0}); + for (auto res : + {"", "ca", "efg", "ea", "ce", "ceb", "eca", "cae", "cea", "bea"}) { + EXPECT_THAT(CappedDamerauLevenshteinDistance("abc", res, 6), uint8_t{3}); + EXPECT_THAT(CappedDamerauLevenshteinDistance(res, "abc", 6), uint8_t{3}); + } + for (auto res : + {"a", "b", "c", "ba", "cb", "bca", "cab", "cba", "ace", + "efc", "ebf", "aef", "ae", "be", "eb", "ec", "ecb", "bec", + "bce", "cbe", "ace", "eac", "aeb", "bae", "eab", "eba"}) { + EXPECT_THAT(CappedDamerauLevenshteinDistance("abc", res, 6), uint8_t{2}); + EXPECT_THAT(CappedDamerauLevenshteinDistance(res, "abc", 6), uint8_t{2}); + } + for (auto res : {"ab", "ac", "bc", "acb", "bac", "ebc", "aec", "abe"}) { + EXPECT_THAT(CappedDamerauLevenshteinDistance("abc", res, 6), uint8_t{1}); + EXPECT_THAT(CappedDamerauLevenshteinDistance(res, "abc", 6), uint8_t{1}); + } +} + +TEST(Distance, TestCutoff) { + // Returing cutoff + 1 if the value is larger than cutoff or string longer + // than MAX_SIZE. + EXPECT_THAT(CappedDamerauLevenshteinDistance("abcd", "a", 3), uint8_t{3}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("abcd", "a", 2), uint8_t{3}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("abcd", "a", 1), uint8_t{2}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("abcdefg", "a", 2), uint8_t{3}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("a", "abcde", 2), uint8_t{3}); + EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(102, 'a'), + std::string(102, 'a'), 105), + uint8_t{101}); + EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(100, 'a'), + std::string(100, 'a'), 100), + uint8_t{0}); + EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(100, 'a'), + std::string(100, 'b'), 100), + uint8_t{100}); + EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(100, 'a'), + std::string(99, 'a'), 2), + uint8_t{1}); + EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(100, 'a'), + std::string(101, 'a'), 2), + uint8_t{3}); + EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(100, 'a'), + std::string(101, 'a'), 2), + uint8_t{3}); + EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(UINT8_MAX + 1, 'a'), + std::string(UINT8_MAX + 1, 'b'), + UINT8_MAX), + uint8_t{101}); + EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(UINT8_MAX - 1, 'a'), + std::string(UINT8_MAX - 1, 'b'), + UINT8_MAX), + uint8_t{101}); + EXPECT_THAT( + CappedDamerauLevenshteinDistance(std::string(UINT8_MAX, 'a'), + std::string(UINT8_MAX, 'b'), UINT8_MAX), + uint8_t{101}); + EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(UINT8_MAX - 1, 'a'), + std::string(UINT8_MAX - 1, 'a'), + UINT8_MAX), + uint8_t{101}); +} +} // namespace diff --git a/absl/strings/internal/has_absl_stringify.h b/absl/strings/internal/has_absl_stringify.h new file mode 100644 index 00000000..55a08508 --- /dev/null +++ b/absl/strings/internal/has_absl_stringify.h @@ -0,0 +1,55 @@ +// Copyright 2022 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ABSL_STRINGS_INTERNAL_HAS_ABSL_STRINGIFY_H_ +#define ABSL_STRINGS_INTERNAL_HAS_ABSL_STRINGIFY_H_ +#include <string> +#include <type_traits> +#include <utility> + +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +namespace strings_internal { + +// This is an empty class not intended to be used. It exists so that +// `HasAbslStringify` can reference a universal class rather than needing to be +// copied for each new sink. +class UnimplementedSink { + public: + void Append(size_t count, char ch); + + void Append(string_view v); + + // Support `absl::Format(&sink, format, args...)`. + friend void AbslFormatFlush(UnimplementedSink* sink, absl::string_view v); +}; + +template <typename T, typename = void> +struct HasAbslStringify : std::false_type {}; + +template <typename T> +struct HasAbslStringify< + T, std::enable_if_t<std::is_void<decltype(AbslStringify( + std::declval<strings_internal::UnimplementedSink&>(), + std::declval<const T&>()))>::value>> : std::true_type {}; + +} // namespace strings_internal + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_STRINGS_INTERNAL_HAS_ABSL_STRINGIFY_H_ diff --git a/absl/strings/internal/str_format/parser.cc b/absl/strings/internal/str_format/parser.cc index f9bb6615..13731ee2 100644 --- a/absl/strings/internal/str_format/parser.cc +++ b/absl/strings/internal/str_format/parser.cc @@ -202,9 +202,7 @@ const char *ConsumeConversion(const char *pos, const char *const end, auto tag = GetTagForChar(c); - if (*(pos - 1) == 'v' && *(pos - 2) != '%') { - return nullptr; - } + if (ABSL_PREDICT_FALSE(c == 'v' && (pos - original_pos) != 1)) return nullptr; if (ABSL_PREDICT_FALSE(!tag.is_conv())) { if (ABSL_PREDICT_FALSE(!tag.is_length())) return nullptr; @@ -223,6 +221,8 @@ const char *ConsumeConversion(const char *pos, const char *const end, conv->length_mod = length_mod; } tag = GetTagForChar(c); + + if (ABSL_PREDICT_FALSE(c == 'v')) return nullptr; if (ABSL_PREDICT_FALSE(!tag.is_conv())) return nullptr; } diff --git a/absl/strings/internal/str_format/parser_test.cc b/absl/strings/internal/str_format/parser_test.cc index fe0d2963..c3e825fe 100644 --- a/absl/strings/internal/str_format/parser_test.cc +++ b/absl/strings/internal/str_format/parser_test.cc @@ -110,10 +110,13 @@ TEST_F(ConsumeUnboundConversionTest, ConsumeSpecification) { {__LINE__, "ba", "", "ba"}, // 'b' is invalid {__LINE__, "l", "", "l" }, // just length mod isn't okay {__LINE__, "d", "d", "" }, // basic + {__LINE__, "v", "v", "" }, // basic {__LINE__, "d ", "d", " " }, // leave suffix {__LINE__, "dd", "d", "d" }, // don't be greedy {__LINE__, "d9", "d", "9" }, // leave non-space suffix {__LINE__, "dzz", "d", "zz"}, // length mod as suffix + {__LINE__, "3v", "", "3v"}, // 'v' cannot have modifiers + {__LINE__, "hv", "", "hv"}, // 'v' cannot have modifiers {__LINE__, "1$*2$d", "1$*2$d", "" }, // arg indexing and * allowed. {__LINE__, "0-14.3hhd", "0-14.3hhd", ""}, // precision, width {__LINE__, " 0-+#14.3hhd", " 0-+#14.3hhd", ""}, // flags diff --git a/absl/strings/internal/stringify_sink.cc b/absl/strings/internal/stringify_sink.cc new file mode 100644 index 00000000..7c6995ab --- /dev/null +++ b/absl/strings/internal/stringify_sink.cc @@ -0,0 +1,28 @@ +// Copyright 2022 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/strings/internal/stringify_sink.h" +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace strings_internal { + +void StringifySink::Append(size_t count, char ch) { buffer_.append(count, ch); } + +void StringifySink::Append(string_view v) { + buffer_.append(v.data(), v.size()); +} + +} // namespace strings_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/strings/internal/stringify_sink.h b/absl/strings/internal/stringify_sink.h new file mode 100644 index 00000000..fc3747bb --- /dev/null +++ b/absl/strings/internal/stringify_sink.h @@ -0,0 +1,57 @@ +// Copyright 2022 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ABSL_STRINGS_INTERNAL_STRINGIFY_SINK_H_ +#define ABSL_STRINGS_INTERNAL_STRINGIFY_SINK_H_ + +#include <string> +#include <type_traits> +#include <utility> + +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +namespace strings_internal { +class StringifySink { + public: + void Append(size_t count, char ch); + + void Append(string_view v); + + // Support `absl::Format(&sink, format, args...)`. + friend void AbslFormatFlush(StringifySink* sink, absl::string_view v) { + sink->Append(v); + } + + private: + template <typename T> + friend string_view ExtractStringification(StringifySink& sink, const T& v); + + std::string buffer_; +}; + +template <typename T> +string_view ExtractStringification(StringifySink& sink, const T& v) { + AbslStringify(sink, v); + return sink.buffer_; +} + +} // namespace strings_internal + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_STRINGS_INTERNAL_STRINGIFY_SINK_H_ diff --git a/absl/strings/str_cat.cc b/absl/strings/str_cat.cc index 6981347a..e5cb6d84 100644 --- a/absl/strings/str_cat.cc +++ b/absl/strings/str_cat.cc @@ -30,39 +30,6 @@ namespace absl { ABSL_NAMESPACE_BEGIN -namespace strings_internal { -void StringifySink::Append(size_t count, char ch) { buffer_.append(count, ch); } - -void StringifySink::Append(string_view v) { - buffer_.append(v.data(), v.size()); -} - -bool StringifySink::PutPaddedString(string_view v, int width, int precision, - bool left) { - size_t space_remaining = 0; - - if (width >= 0) space_remaining = static_cast<size_t>(width); - - size_t n = v.size(); - - if (precision >= 0) n = (std::min)(n, static_cast<size_t>(precision)); - - string_view shown(v.data(), n); - - if (shown.size() < space_remaining) { - space_remaining = space_remaining - shown.size(); - } else { - space_remaining = 0; - } - - if (!left) Append(space_remaining, ' '); - Append(shown); - if (left) Append(space_remaining, ' '); - return true; -} - -} // namespace strings_internal - AlphaNum::AlphaNum(Hex hex) { static_assert(numbers_internal::kFastToBufferSize >= 32, "This function only works when output buffer >= 32 bytes long"); diff --git a/absl/strings/str_cat.h b/absl/strings/str_cat.h index 05728ab5..00af84ea 100644 --- a/absl/strings/str_cat.h +++ b/absl/strings/str_cat.h @@ -48,6 +48,40 @@ // `StrCat()` or `StrAppend()`. You may specify a minimum hex field width using // a `PadSpec` enum. // +// User-defined types can be formatted with the `AbslStringify()` customization +// point. The API relies on detecting an overload in the user-defined type's +// namespace of a free (non-member) `AbslStringify()` function as a definition +// (typically declared as a friend and implemented in-line. +// with the following signature: +// +// class MyClass { ... }; +// +// template <typename Sink> +// void AbslStringify(Sink& sink, const MyClass& value); +// +// An `AbslStringify()` overload for a type should only be declared in the same +// file and namespace as said type. +// +// Note that `AbslStringify()` also supports use with `absl::StrFormat()` and +// `absl::Substitute()`. +// +// Example: +// +// struct Point { +// // To add formatting support to `Point`, we simply need to add a free +// // (non-member) function `AbslStringify()`. This method specifies how +// // Point should be printed when absl::StrCat() is called on it. You can add +// // such a free function using a friend declaration within the body of the +// // class. The sink parameter is a templated type to avoid requiring +// // dependencies. +// template <typename Sink> friend void AbslStringify(Sink& +// sink, const Point& p) { +// absl::Format(&sink, "(%v, %v)", p.x, p.y); +// } +// +// int x; +// int y; +// }; // ----------------------------------------------------------------------------- #ifndef ABSL_STRINGS_STR_CAT_H_ @@ -61,6 +95,8 @@ #include <vector> #include "absl/base/port.h" +#include "absl/strings/internal/has_absl_stringify.h" +#include "absl/strings/internal/stringify_sink.h" #include "absl/strings/numbers.h" #include "absl/strings/string_view.h" @@ -77,27 +113,6 @@ struct AlphaNumBuffer { size_t size; }; -class StringifySink { - public: - void Append(size_t count, char ch); - - void Append(string_view v); - - bool PutPaddedString(string_view v, int width, int precision, bool left); - - template <typename T> - friend string_view ExtractStringification(StringifySink& sink, const T& v); - - private: - std::string buffer_; -}; - -template <typename T> -string_view ExtractStringification(StringifySink& sink, const T& v) { - AbslStringify(sink, v); - return sink.buffer_; -} - } // namespace strings_internal // Enum that specifies the number of significant digits to return in a `Hex` or @@ -230,15 +245,6 @@ struct Dec { // `StrAppend()`, providing efficient conversion of numeric, boolean, and // hexadecimal values (through the `Hex` type) into strings. -template <typename T, typename = void> -struct HasAbslStringify : std::false_type {}; - -template <typename T> -struct HasAbslStringify<T, std::enable_if_t<std::is_void<decltype(AbslStringify( - std::declval<strings_internal::StringifySink&>(), - std::declval<const T&>()))>::value>> - : std::true_type {}; - class AlphaNum { public: // No bool ctor -- bools convert to an integral type. @@ -287,7 +293,7 @@ class AlphaNum { AlphaNum(absl::string_view pc) : piece_(pc) {} // NOLINT(runtime/explicit) template <typename T, typename = typename std::enable_if< - HasAbslStringify<T>::value>::type> + strings_internal::HasAbslStringify<T>::value>::type> AlphaNum( // NOLINT(runtime/explicit) const T& v, // NOLINT(runtime/explicit) strings_internal::StringifySink&& sink = {}) // NOLINT(runtime/explicit) diff --git a/absl/strings/str_cat_test.cc b/absl/strings/str_cat_test.cc index 868b9bce..1b3b7ece 100644 --- a/absl/strings/str_cat_test.cc +++ b/absl/strings/str_cat_test.cc @@ -21,6 +21,7 @@ #include <vector> #include "gtest/gtest.h" +#include "absl/strings/str_format.h" #include "absl/strings/substitute.h" #ifdef __ANDROID__ @@ -632,4 +633,21 @@ TEST(StrCat, AbslStringifyExample) { EXPECT_EQ(absl::StrCat("a ", p, " z"), "a (10, 20) z"); } +struct PointStringifyUsingFormat { + template <typename FormatSink> + friend void AbslStringify(FormatSink& sink, + const PointStringifyUsingFormat& p) { + absl::Format(&sink, "(%g, %g)", p.x, p.y); + } + + double x = 10.0; + double y = 20.0; +}; + +TEST(StrCat, AbslStringifyExampleUsingFormat) { + PointStringifyUsingFormat p; + EXPECT_EQ(absl::StrCat(p), "(10, 20)"); + EXPECT_EQ(absl::StrCat("a ", p, " z"), "a (10, 20) z"); +} + } // namespace diff --git a/absl/strings/str_format.h b/absl/strings/str_format.h index e6537ea0..43d86186 100644 --- a/absl/strings/str_format.h +++ b/absl/strings/str_format.h @@ -570,6 +570,41 @@ ABSL_MUST_USE_RESULT inline bool FormatUntyped( // StrFormat Extensions //------------------------------------------------------------------------------ // +// AbslStringify() +// +// A simpler customization API for formatting user-defined types using +// absl::StrFormat(). The API relies on detecting an overload in the +// user-defined type's namespace of a free (non-member) `AbslStringify()` +// function as a friend definition with the following signature: +// +// template <typename Sink> +// void AbslStringify(Sink& sink, const X& value); +// +// An `AbslStringify()` overload for a type should only be declared in the same +// file and namespace as said type. +// +// Note that unlike with AbslFormatConvert(), AbslStringify() does not allow +// customization of allowed conversion characters. AbslStringify() uses `%v` as +// the underlying conversion specififer. Additionally, AbslStringify() supports +// use with absl::StrCat while AbslFormatConvert() does not. +// +// Example: +// +// struct Point { +// // To add formatting support to `Point`, we simply need to add a free +// // (non-member) function `AbslStringify()`. This method prints in the +// // request format using the underlying `%v` specifier. You can add such a +// // free function using a friend declaration within the body of the class. +// // The sink parameter is a templated type to avoid requiring dependencies. +// template <typename Sink> +// friend void AbslStringify(Sink& sink, const Point& p) { +// absl::Format(&sink, "(%v, %v)", p.x, p.y); +// } +// +// int x; +// int y; +// }; +// // AbslFormatConvert() // // The StrFormat library provides a customization API for formatting @@ -616,9 +651,9 @@ ABSL_MUST_USE_RESULT inline bool FormatUntyped( // AbslFormatConvert(const Point& p, const absl::FormatConversionSpec& spec, // absl::FormatSink* s) { // if (spec.conversion_char() == absl::FormatConversionChar::s) { -// s->Append(absl::StrCat("x=", p.x, " y=", p.y)); +// absl::Format(s, "x=%vy=%v", p.x, p.y); // } else { -// s->Append(absl::StrCat(p.x, ",", p.y)); +// absl::Format(s, "%v,%v", p.x, p.y); // } // return {true}; // } @@ -789,6 +824,11 @@ class FormatSink { return sink_->PutPaddedString(v, width, precision, left); } + // Support `absl::Format(&sink, format, args...)`. + friend void AbslFormatFlush(FormatSink* sink, absl::string_view v) { + sink->Append(v); + } + private: friend str_format_internal::FormatSinkImpl; explicit FormatSink(str_format_internal::FormatSinkImpl* s) : sink_(s) {} diff --git a/absl/strings/str_format_test.cc b/absl/strings/str_format_test.cc index 0c4f10c8..62ed262d 100644 --- a/absl/strings/str_format_test.cc +++ b/absl/strings/str_format_test.cc @@ -1118,6 +1118,23 @@ TEST_F(FormatExtensionTest, AbslStringifyExample) { PointStringify p; EXPECT_EQ(absl::StrFormat("a %v z", p), "a (10, 20) z"); } + +struct PointStringifyUsingFormat { + template <typename FormatSink> + friend void AbslStringify(FormatSink& sink, + const PointStringifyUsingFormat& p) { + absl::Format(&sink, "(%g, %g)", p.x, p.y); + } + + double x = 10.0; + double y = 20.0; +}; + +TEST_F(FormatExtensionTest, AbslStringifyExampleUsingFormat) { + PointStringifyUsingFormat p; + EXPECT_EQ(absl::StrFormat("a %v z", p), "a (10, 20) z"); +} + } // namespace // Some codegen thunks that we can use to easily dump the generated assembly for diff --git a/absl/strings/substitute.h b/absl/strings/substitute.h index 692fd03c..5c3f6eff 100644 --- a/absl/strings/substitute.h +++ b/absl/strings/substitute.h @@ -55,6 +55,8 @@ // * bool (Printed as "true" or "false") // * pointer types other than char* (Printed as "0x<lower case hex string>", // except that null is printed as "NULL") +// * user-defined types via the `AbslStringify()` customization point. See the +// documentation for `absl::StrCat` for an explanation on how to use this. // // If an invalid format string is provided, Substitute returns an empty string // and SubstituteAndAppend does not change the provided output string. @@ -79,6 +81,7 @@ #include "absl/base/port.h" #include "absl/strings/ascii.h" #include "absl/strings/escaping.h" +#include "absl/strings/internal/stringify_sink.h" #include "absl/strings/numbers.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_split.h" @@ -102,14 +105,14 @@ class Arg { // Overloads for string-y things // // Explicitly overload `const char*` so the compiler doesn't cast to `bool`. - Arg(const char* value) // NOLINT(runtime/explicit) + Arg(const char* value) // NOLINT(google-explicit-constructor) : piece_(absl::NullSafeStringView(value)) {} template <typename Allocator> Arg( // NOLINT const std::basic_string<char, std::char_traits<char>, Allocator>& value) noexcept : piece_(value) {} - Arg(absl::string_view value) // NOLINT(runtime/explicit) + Arg(absl::string_view value) // NOLINT(google-explicit-constructor) : piece_(value) {} // Overloads for primitives @@ -119,7 +122,7 @@ class Arg { // probably using them as 8-bit integers and would probably prefer an integer // representation. However, we can't really know, so we make the caller decide // what to do. - Arg(char value) // NOLINT(runtime/explicit) + Arg(char value) // NOLINT(google-explicit-constructor) : piece_(scratch_, 1) { scratch_[0] = value; } @@ -133,12 +136,12 @@ class Arg { static_cast<size_t>( numbers_internal::FastIntToBuffer(value, scratch_) - scratch_)) {} - Arg(int value) // NOLINT(runtime/explicit) + Arg(int value) // NOLINT(google-explicit-constructor) : piece_(scratch_, static_cast<size_t>( numbers_internal::FastIntToBuffer(value, scratch_) - scratch_)) {} - Arg(unsigned int value) // NOLINT(runtime/explicit) + Arg(unsigned int value) // NOLINT(google-explicit-constructor) : piece_(scratch_, static_cast<size_t>( numbers_internal::FastIntToBuffer(value, scratch_) - @@ -163,17 +166,23 @@ class Arg { static_cast<size_t>( numbers_internal::FastIntToBuffer(value, scratch_) - scratch_)) {} - Arg(float value) // NOLINT(runtime/explicit) + Arg(float value) // NOLINT(google-explicit-constructor) : piece_(scratch_, numbers_internal::SixDigitsToBuffer(value, scratch_)) { } - Arg(double value) // NOLINT(runtime/explicit) + Arg(double value) // NOLINT(google-explicit-constructor) : piece_(scratch_, numbers_internal::SixDigitsToBuffer(value, scratch_)) { } - Arg(bool value) // NOLINT(runtime/explicit) + Arg(bool value) // NOLINT(google-explicit-constructor) : piece_(value ? "true" : "false") {} - Arg(Hex hex); // NOLINT(runtime/explicit) - Arg(Dec dec); // NOLINT(runtime/explicit) + template <typename T, typename = typename std::enable_if< + strings_internal::HasAbslStringify<T>::value>::type> + Arg( // NOLINT(google-explicit-constructor) + const T& v, strings_internal::StringifySink&& sink = {}) + : piece_(strings_internal::ExtractStringification(sink, v)) {} + + Arg(Hex hex); // NOLINT(google-explicit-constructor) + Arg(Dec dec); // NOLINT(google-explicit-constructor) // vector<bool>::reference and const_reference require special help to convert // to `Arg` because it requires two user defined conversions. @@ -188,7 +197,7 @@ class Arg { // `void*` values, with the exception of `char*`, are printed as // "0x<hex value>". However, in the case of `nullptr`, "NULL" is printed. - Arg(const void* value); // NOLINT(runtime/explicit) + Arg(const void* value); // NOLINT(google-explicit-constructor) // Normal enums are already handled by the integer formatters. // This overload matches only scoped enums. diff --git a/absl/strings/substitute_test.cc b/absl/strings/substitute_test.cc index 9e6b9403..9f04545f 100644 --- a/absl/strings/substitute_test.cc +++ b/absl/strings/substitute_test.cc @@ -22,6 +22,16 @@ namespace { +struct MyStruct { + template <typename Sink> + friend void AbslStringify(Sink& sink, const MyStruct& s) { + sink.Append("MyStruct{.value = "); + sink.Append(absl::StrCat(s.value)); + sink.Append("}"); + } + int value; +}; + TEST(SubstituteTest, Substitute) { // Basic. EXPECT_EQ("Hello, world!", absl::Substitute("$0, $1!", "Hello", "world")); @@ -70,7 +80,7 @@ TEST(SubstituteTest, Substitute) { // Volatile Pointer. // Like C++ streamed I/O, such pointers implicitly become bool volatile int vol = 237; - volatile int *volatile volptr = &vol; + volatile int* volatile volptr = &vol; str = absl::Substitute("$0", volptr); EXPECT_EQ("true", str); @@ -128,6 +138,11 @@ TEST(SubstituteTest, Substitute) { const char* null_cstring = nullptr; EXPECT_EQ("Text: ''", absl::Substitute("Text: '$0'", null_cstring)); + + MyStruct s1 = MyStruct{17}; + MyStruct s2 = MyStruct{1043}; + EXPECT_EQ("MyStruct{.value = 17}, MyStruct{.value = 1043}", + absl::Substitute("$0, $1", s1, s2)); } TEST(SubstituteTest, SubstituteAndAppend) { @@ -171,6 +186,12 @@ TEST(SubstituteTest, SubstituteAndAppend) { absl::SubstituteAndAppend(&str, "$0 $1 $2 $3 $4 $5 $6 $7 $8 $9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j"); EXPECT_EQ("a b c d e f g h i j", str); + + str.clear(); + MyStruct s1 = MyStruct{17}; + MyStruct s2 = MyStruct{1043}; + absl::SubstituteAndAppend(&str, "$0, $1", s1, s2); + EXPECT_EQ("MyStruct{.value = 17}, MyStruct{.value = 1043}", str); } TEST(SubstituteTest, VectorBoolRef) { diff --git a/absl/synchronization/BUILD.bazel b/absl/synchronization/BUILD.bazel index 3a378160..66bd8742 100644 --- a/absl/synchronization/BUILD.bazel +++ b/absl/synchronization/BUILD.bazel @@ -286,7 +286,7 @@ cc_library( cc_test( name = "per_thread_sem_test", - size = "medium", + size = "large", copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, tags = [ diff --git a/absl/synchronization/lifetime_test.cc b/absl/synchronization/lifetime_test.cc index cc973a32..e6274232 100644 --- a/absl/synchronization/lifetime_test.cc +++ b/absl/synchronization/lifetime_test.cc @@ -123,10 +123,10 @@ class OnDestruction { }; // These tests require that the compiler correctly supports C++11 constant -// initialization... but MSVC has a known regression since v19.10: +// initialization... but MSVC has a known regression since v19.10 till v19.25: // https://developercommunity.visualstudio.com/content/problem/336946/class-with-constexpr-constructor-not-using-static.html -// TODO(epastor): Limit the affected range once MSVC fixes this bug. -#if defined(__clang__) || !(defined(_MSC_VER) && _MSC_VER > 1900) +#if defined(__clang__) || \ + !(defined(_MSC_VER) && _MSC_VER > 1900 && _MSC_VER < 1925) // kConstInit // Test early usage. (Declaration comes first; definitions must appear after // the test runner.) diff --git a/absl/synchronization/mutex.cc b/absl/synchronization/mutex.cc index c0268b62..b0f412bf 100644 --- a/absl/synchronization/mutex.cc +++ b/absl/synchronization/mutex.cc @@ -135,25 +135,42 @@ enum DelayMode { AGGRESSIVE, GENTLE }; struct ABSL_CACHELINE_ALIGNED MutexGlobals { absl::once_flag once; int spinloop_iterations = 0; - int32_t mutex_sleep_limit[2] = {}; + int32_t mutex_sleep_spins[2] = {}; + absl::Duration mutex_sleep_time; }; +absl::Duration MeasureTimeToYield() { + absl::Time before = absl::Now(); + ABSL_INTERNAL_C_SYMBOL(AbslInternalMutexYield)(); + return absl::Now() - before; +} + const MutexGlobals &GetMutexGlobals() { ABSL_CONST_INIT static MutexGlobals data; absl::base_internal::LowLevelCallOnce(&data.once, [&]() { const int num_cpus = absl::base_internal::NumCPUs(); data.spinloop_iterations = num_cpus > 1 ? 1500 : 0; - // If this a uniprocessor, only yield/sleep. Otherwise, if the mode is + // If this a uniprocessor, only yield/sleep. + // Real-time threads are often unable to yield, so the sleep time needs + // to be long enough to keep the calling thread asleep until scheduling + // happens. + // If this is multiprocessor, allow spinning. If the mode is // aggressive then spin many times before yielding. If the mode is // gentle then spin only a few times before yielding. Aggressive spinning // is used to ensure that an Unlock() call, which must get the spin lock // for any thread to make progress gets it without undue delay. if (num_cpus > 1) { - data.mutex_sleep_limit[AGGRESSIVE] = 5000; - data.mutex_sleep_limit[GENTLE] = 250; + data.mutex_sleep_spins[AGGRESSIVE] = 5000; + data.mutex_sleep_spins[GENTLE] = 250; + data.mutex_sleep_time = absl::Microseconds(10); } else { - data.mutex_sleep_limit[AGGRESSIVE] = 0; - data.mutex_sleep_limit[GENTLE] = 0; + data.mutex_sleep_spins[AGGRESSIVE] = 0; + data.mutex_sleep_spins[GENTLE] = 0; + data.mutex_sleep_time = MeasureTimeToYield() * 5; + data.mutex_sleep_time = + std::min(data.mutex_sleep_time, absl::Milliseconds(1)); + data.mutex_sleep_time = + std::max(data.mutex_sleep_time, absl::Microseconds(10)); } }); return data; @@ -164,7 +181,8 @@ namespace synchronization_internal { // Returns the Mutex delay on iteration `c` depending on the given `mode`. // The returned value should be used as `c` for the next call to `MutexDelay`. int MutexDelay(int32_t c, int mode) { - const int32_t limit = GetMutexGlobals().mutex_sleep_limit[mode]; + const int32_t limit = GetMutexGlobals().mutex_sleep_spins[mode]; + const absl::Duration sleep_time = GetMutexGlobals().mutex_sleep_time; if (c < limit) { // Spin. c++; @@ -177,7 +195,7 @@ int MutexDelay(int32_t c, int mode) { c++; } else { // Then wait. - absl::SleepFor(absl::Microseconds(10)); + absl::SleepFor(sleep_time); c = 0; } ABSL_TSAN_MUTEX_POST_DIVERT(nullptr, 0); @@ -2342,22 +2360,26 @@ ABSL_ATTRIBUTE_NOINLINE void Mutex::UnlockSlow(SynchWaitParams *waitp) { } // end of for(;;)-loop if (wake_list != kPerThreadSynchNull) { - int64_t wait_cycles = 0; + int64_t total_wait_cycles = 0; + int64_t max_wait_cycles = 0; int64_t now = base_internal::CycleClock::Now(); do { - // Sample lock contention events only if the waiter was trying to acquire + // Profile lock contention events only if the waiter was trying to acquire // the lock, not waiting on a condition variable or Condition. if (!wake_list->cond_waiter) { - wait_cycles += (now - wake_list->waitp->contention_start_cycles); + int64_t cycles_waited = + (now - wake_list->waitp->contention_start_cycles); + total_wait_cycles += cycles_waited; + if (max_wait_cycles == 0) max_wait_cycles = cycles_waited; wake_list->waitp->contention_start_cycles = now; wake_list->waitp->should_submit_contention_data = true; } wake_list = Wakeup(wake_list); // wake waiters } while (wake_list != kPerThreadSynchNull); - if (wait_cycles > 0) { - mutex_tracer("slow release", this, wait_cycles); + if (total_wait_cycles > 0) { + mutex_tracer("slow release", this, total_wait_cycles); ABSL_TSAN_MUTEX_PRE_DIVERT(this, 0); - submit_profile_data(wait_cycles); + submit_profile_data(total_wait_cycles); ABSL_TSAN_MUTEX_POST_DIVERT(this, 0); } } diff --git a/absl/time/BUILD.bazel b/absl/time/BUILD.bazel index aa07df3d..b5f03980 100644 --- a/absl/time/BUILD.bazel +++ b/absl/time/BUILD.bazel @@ -45,12 +45,14 @@ cc_library( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ "//absl/base", + "//absl/base:config", "//absl/base:core_headers", "//absl/base:raw_logging_internal", "//absl/numeric:int128", "//absl/strings", "//absl/time/internal/cctz:civil_time", "//absl/time/internal/cctz:time_zone", + "//absl/types:optional", ], ) @@ -98,6 +100,30 @@ cc_test( ) cc_test( + name = "flag_test", + srcs = [ + "flag_test.cc", + ], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + tags = [ + "no_test_android_arm", + "no_test_android_arm64", + "no_test_android_x86", + "no_test_ios_x86_64", + "no_test_loonix", + "no_test_msvc_x64", + "no_test_wasm", + ], + deps = [ + ":time", + "//absl/flags:flag", + "//absl/flags:reflection", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( name = "time_benchmark", srcs = [ "civil_time_benchmark.cc", diff --git a/absl/time/CMakeLists.txt b/absl/time/CMakeLists.txt index debab3ba..1580a303 100644 --- a/absl/time/CMakeLists.txt +++ b/absl/time/CMakeLists.txt @@ -127,3 +127,16 @@ absl_cc_test( absl::time_zone GTest::gmock_main ) + +absl_cc_test( + NAME + flag_test + SRCS + "flag_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::flags + absl::flags_reflection + GTest::gmock_main +) diff --git a/absl/time/civil_time.cc b/absl/time/civil_time.cc index 6a231edb..65df39d7 100644 --- a/absl/time/civil_time.cc +++ b/absl/time/civil_time.cc @@ -15,6 +15,7 @@ #include "absl/time/civil_time.h" #include <cstdlib> +#include <ostream> #include <string> #include "absl/strings/str_cat.h" @@ -167,6 +168,31 @@ std::ostream& operator<<(std::ostream& os, CivilSecond s) { return os << FormatCivilTime(s); } +bool AbslParseFlag(string_view s, CivilSecond* c, std::string*) { + return ParseLenientCivilTime(s, c); +} +bool AbslParseFlag(string_view s, CivilMinute* c, std::string*) { + return ParseLenientCivilTime(s, c); +} +bool AbslParseFlag(string_view s, CivilHour* c, std::string*) { + return ParseLenientCivilTime(s, c); +} +bool AbslParseFlag(string_view s, CivilDay* c, std::string*) { + return ParseLenientCivilTime(s, c); +} +bool AbslParseFlag(string_view s, CivilMonth* c, std::string*) { + return ParseLenientCivilTime(s, c); +} +bool AbslParseFlag(string_view s, CivilYear* c, std::string*) { + return ParseLenientCivilTime(s, c); +} +std::string AbslUnparseFlag(CivilSecond c) { return FormatCivilTime(c); } +std::string AbslUnparseFlag(CivilMinute c) { return FormatCivilTime(c); } +std::string AbslUnparseFlag(CivilHour c) { return FormatCivilTime(c); } +std::string AbslUnparseFlag(CivilDay c) { return FormatCivilTime(c); } +std::string AbslUnparseFlag(CivilMonth c) { return FormatCivilTime(c); } +std::string AbslUnparseFlag(CivilYear c) { return FormatCivilTime(c); } + } // namespace time_internal ABSL_NAMESPACE_END diff --git a/absl/time/civil_time.h b/absl/time/civil_time.h index bb460044..5855bc73 100644 --- a/absl/time/civil_time.h +++ b/absl/time/civil_time.h @@ -70,8 +70,10 @@ #ifndef ABSL_TIME_CIVIL_TIME_H_ #define ABSL_TIME_CIVIL_TIME_H_ +#include <iosfwd> #include <string> +#include "absl/base/config.h" #include "absl/strings/string_view.h" #include "absl/time/internal/cctz/include/cctz/civil_time.h" @@ -530,6 +532,29 @@ std::ostream& operator<<(std::ostream& os, CivilHour h); std::ostream& operator<<(std::ostream& os, CivilMinute m); std::ostream& operator<<(std::ostream& os, CivilSecond s); +// AbslParseFlag() +// +// Parses the command-line flag string representation `s` into a civil-time +// value. Flags must be specified in a format that is valid for +// `absl::ParseLenientCivilTime()`. +bool AbslParseFlag(absl::string_view s, CivilSecond* c, std::string* error); +bool AbslParseFlag(absl::string_view s, CivilMinute* c, std::string* error); +bool AbslParseFlag(absl::string_view s, CivilHour* c, std::string* error); +bool AbslParseFlag(absl::string_view s, CivilDay* c, std::string* error); +bool AbslParseFlag(absl::string_view s, CivilMonth* c, std::string* error); +bool AbslParseFlag(absl::string_view s, CivilYear* c, std::string* error); + +// AbslUnparseFlag() +// +// Unparses a civil-time value into a command-line string representation using +// the format specified by `absl::ParseCivilTime()`. +std::string AbslUnparseFlag(CivilSecond c); +std::string AbslUnparseFlag(CivilMinute c); +std::string AbslUnparseFlag(CivilHour c); +std::string AbslUnparseFlag(CivilDay c); +std::string AbslUnparseFlag(CivilMonth c); +std::string AbslUnparseFlag(CivilYear c); + } // namespace time_internal ABSL_NAMESPACE_END diff --git a/absl/time/flag_test.cc b/absl/time/flag_test.cc new file mode 100644 index 00000000..8f8532b7 --- /dev/null +++ b/absl/time/flag_test.cc @@ -0,0 +1,147 @@ +// 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/flags/flag.h" + +#include <string> + +#include "gtest/gtest.h" +#include "absl/flags/reflection.h" +#include "absl/time/civil_time.h" +#include "absl/time/time.h" + +ABSL_FLAG(absl::CivilSecond, test_flag_civil_second, + absl::CivilSecond(2015, 1, 2, 3, 4, 5), ""); +ABSL_FLAG(absl::CivilMinute, test_flag_civil_minute, + absl::CivilMinute(2015, 1, 2, 3, 4), ""); +ABSL_FLAG(absl::CivilHour, test_flag_civil_hour, absl::CivilHour(2015, 1, 2, 3), + ""); +ABSL_FLAG(absl::CivilDay, test_flag_civil_day, absl::CivilDay(2015, 1, 2), ""); +ABSL_FLAG(absl::CivilMonth, test_flag_civil_month, absl::CivilMonth(2015, 1), + ""); +ABSL_FLAG(absl::CivilYear, test_flag_civil_year, absl::CivilYear(2015), ""); + +ABSL_FLAG(absl::Duration, test_duration_flag, absl::Seconds(5), + "For testing support for Duration flags"); +ABSL_FLAG(absl::Time, test_time_flag, absl::InfinitePast(), + "For testing support for Time flags"); + +namespace { + +bool SetFlagValue(absl::string_view flag_name, absl::string_view value) { + auto* flag = absl::FindCommandLineFlag(flag_name); + if (!flag) return false; + std::string err; + return flag->ParseFrom(value, &err); +} + +bool GetFlagValue(absl::string_view flag_name, std::string& value) { + auto* flag = absl::FindCommandLineFlag(flag_name); + if (!flag) return false; + value = flag->CurrentValue(); + return true; +} + +TEST(CivilTime, FlagSupport) { + // Tests the default setting of the flags. + const absl::CivilSecond kDefaultSec(2015, 1, 2, 3, 4, 5); + EXPECT_EQ(absl::CivilSecond(kDefaultSec), + absl::GetFlag(FLAGS_test_flag_civil_second)); + EXPECT_EQ(absl::CivilMinute(kDefaultSec), + absl::GetFlag(FLAGS_test_flag_civil_minute)); + EXPECT_EQ(absl::CivilHour(kDefaultSec), + absl::GetFlag(FLAGS_test_flag_civil_hour)); + EXPECT_EQ(absl::CivilDay(kDefaultSec), + absl::GetFlag(FLAGS_test_flag_civil_day)); + EXPECT_EQ(absl::CivilMonth(kDefaultSec), + absl::GetFlag(FLAGS_test_flag_civil_month)); + EXPECT_EQ(absl::CivilYear(kDefaultSec), + absl::GetFlag(FLAGS_test_flag_civil_year)); + + // Sets flags to a new value. + const absl::CivilSecond kNewSec(2016, 6, 7, 8, 9, 10); + absl::SetFlag(&FLAGS_test_flag_civil_second, absl::CivilSecond(kNewSec)); + absl::SetFlag(&FLAGS_test_flag_civil_minute, absl::CivilMinute(kNewSec)); + absl::SetFlag(&FLAGS_test_flag_civil_hour, absl::CivilHour(kNewSec)); + absl::SetFlag(&FLAGS_test_flag_civil_day, absl::CivilDay(kNewSec)); + absl::SetFlag(&FLAGS_test_flag_civil_month, absl::CivilMonth(kNewSec)); + absl::SetFlag(&FLAGS_test_flag_civil_year, absl::CivilYear(kNewSec)); + + EXPECT_EQ(absl::CivilSecond(kNewSec), + absl::GetFlag(FLAGS_test_flag_civil_second)); + EXPECT_EQ(absl::CivilMinute(kNewSec), + absl::GetFlag(FLAGS_test_flag_civil_minute)); + EXPECT_EQ(absl::CivilHour(kNewSec), + absl::GetFlag(FLAGS_test_flag_civil_hour)); + EXPECT_EQ(absl::CivilDay(kNewSec), absl::GetFlag(FLAGS_test_flag_civil_day)); + EXPECT_EQ(absl::CivilMonth(kNewSec), + absl::GetFlag(FLAGS_test_flag_civil_month)); + EXPECT_EQ(absl::CivilYear(kNewSec), + absl::GetFlag(FLAGS_test_flag_civil_year)); +} + +TEST(Duration, FlagSupport) { + EXPECT_EQ(absl::Seconds(5), absl::GetFlag(FLAGS_test_duration_flag)); + + absl::SetFlag(&FLAGS_test_duration_flag, absl::Seconds(10)); + EXPECT_EQ(absl::Seconds(10), absl::GetFlag(FLAGS_test_duration_flag)); + + EXPECT_TRUE(SetFlagValue("test_duration_flag", "20s")); + EXPECT_EQ(absl::Seconds(20), absl::GetFlag(FLAGS_test_duration_flag)); + + std::string current_flag_value; + EXPECT_TRUE(GetFlagValue("test_duration_flag", current_flag_value)); + EXPECT_EQ("20s", current_flag_value); +} + +TEST(Time, FlagSupport) { + EXPECT_EQ(absl::InfinitePast(), absl::GetFlag(FLAGS_test_time_flag)); + + const absl::Time t = absl::FromCivil(absl::CivilSecond(2016, 1, 2, 3, 4, 5), + absl::UTCTimeZone()); + absl::SetFlag(&FLAGS_test_time_flag, t); + EXPECT_EQ(t, absl::GetFlag(FLAGS_test_time_flag)); + + // Successful parse + EXPECT_TRUE(SetFlagValue("test_time_flag", "2016-01-02T03:04:06Z")); + EXPECT_EQ(t + absl::Seconds(1), absl::GetFlag(FLAGS_test_time_flag)); + EXPECT_TRUE(SetFlagValue("test_time_flag", "2016-01-02T03:04:07.0Z")); + EXPECT_EQ(t + absl::Seconds(2), absl::GetFlag(FLAGS_test_time_flag)); + EXPECT_TRUE(SetFlagValue("test_time_flag", "2016-01-02T03:04:08.000Z")); + EXPECT_EQ(t + absl::Seconds(3), absl::GetFlag(FLAGS_test_time_flag)); + EXPECT_TRUE(SetFlagValue("test_time_flag", "2016-01-02T03:04:09+00:00")); + EXPECT_EQ(t + absl::Seconds(4), absl::GetFlag(FLAGS_test_time_flag)); + EXPECT_TRUE(SetFlagValue("test_time_flag", "2016-01-02T03:04:05.123+00:00")); + EXPECT_EQ(t + absl::Milliseconds(123), absl::GetFlag(FLAGS_test_time_flag)); + EXPECT_TRUE(SetFlagValue("test_time_flag", "2016-01-02T03:04:05.123+08:00")); + EXPECT_EQ(t + absl::Milliseconds(123) - absl::Hours(8), + absl::GetFlag(FLAGS_test_time_flag)); + EXPECT_TRUE(SetFlagValue("test_time_flag", "infinite-future")); + EXPECT_EQ(absl::InfiniteFuture(), absl::GetFlag(FLAGS_test_time_flag)); + EXPECT_TRUE(SetFlagValue("test_time_flag", "infinite-past")); + EXPECT_EQ(absl::InfinitePast(), absl::GetFlag(FLAGS_test_time_flag)); + + EXPECT_FALSE(SetFlagValue("test_time_flag", "2016-01-02T03:04:06")); + EXPECT_FALSE(SetFlagValue("test_time_flag", "2016-01-02")); + EXPECT_FALSE(SetFlagValue("test_time_flag", "2016-01-02Z")); + EXPECT_FALSE(SetFlagValue("test_time_flag", "2016-01-02+00:00")); + EXPECT_FALSE(SetFlagValue("test_time_flag", "2016-99-99T03:04:06Z")); + + EXPECT_TRUE(SetFlagValue("test_time_flag", "2016-01-02T03:04:05Z")); + std::string current_flag_value; + EXPECT_TRUE(GetFlagValue("test_time_flag", current_flag_value)); + EXPECT_EQ("2016-01-02T03:04:05+00:00", current_flag_value); +} + +} // namespace diff --git a/absl/time/internal/cctz/src/time_zone_info.cc b/absl/time/internal/cctz/src/time_zone_info.cc index 8966f7ac..787426f7 100644 --- a/absl/time/internal/cctz/src/time_zone_info.cc +++ b/absl/time/internal/cctz/src/time_zone_info.cc @@ -502,9 +502,9 @@ bool TimeZoneInfo::Load(ZoneInfoSource* zip) { // encoded zoneinfo. The ttisstd/ttisgmt indicators only apply when // interpreting a POSIX spec that does not include start/end rules, and // that isn't the case here (see "zic -p"). - bp += (8 + 4) * hdr.leapcnt; // leap-time + TAI-UTC - bp += 1 * hdr.ttisstdcnt; // UTC/local indicators - bp += 1 * hdr.ttisutcnt; // standard/wall indicators + bp += (time_len + 4) * hdr.leapcnt; // leap-time + TAI-UTC + bp += 1 * hdr.ttisstdcnt; // UTC/local indicators + bp += 1 * hdr.ttisutcnt; // standard/wall indicators assert(bp == tbuf.data() + tbuf.size()); future_spec_.clear(); @@ -533,8 +533,8 @@ bool TimeZoneInfo::Load(ZoneInfoSource* zip) { // 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). - // For us, they just get in the way when we do future_spec_ extension. + // zic.c:dontmerge) or to avoid bugs in old readers. For us, they just + // get in the way when we do future_spec_ extension. while (hdr.timecnt > 1) { if (!EquivTransitions(transitions_[hdr.timecnt - 1].type_index, transitions_[hdr.timecnt - 2].type_index)) { diff --git a/absl/time/internal/cctz/src/zone_info_source.cc b/absl/time/internal/cctz/src/zone_info_source.cc index 5ab5a59e..b818c213 100644 --- a/absl/time/internal/cctz/src/zone_info_source.cc +++ b/absl/time/internal/cctz/src/zone_info_source.cc @@ -66,41 +66,41 @@ extern ZoneInfoSourceFactory zone_info_source_factory; extern ZoneInfoSourceFactory default_factory; ZoneInfoSourceFactory default_factory = DefaultFactory; #if defined(_M_IX86) || defined(_M_ARM) -#pragma comment( \ - linker, \ - "/alternatename:?zone_info_source_factory@cctz_extension@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@3P6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@@std@@@std@@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ - "@ABV?$function@$$A6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@@std@@@std@@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@@Z@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ - "@@ZA=?default_factory@cctz_extension@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@3P6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@@std@@@std@@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ - "@ABV?$function@$$A6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@@std@@@std@@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@@Z@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ - "@@ZA") +#pragma comment( \ + linker, \ + "/alternatename:?zone_info_source_factory@cctz_extension@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@3P6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@@std@@@std@@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ + "@ABV?$function@$$A6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@@std@@@std@@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@@Z@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ + "@@ZA=?default_factory@cctz_extension@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@3P6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@@std@@@std@@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ + "@ABV?$function@$$A6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@@std@@@std@@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@@Z@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ + "@@ZA") #elif defined(_M_IA_64) || defined(_M_AMD64) || defined(_M_ARM64) -#pragma comment( \ - linker, \ - "/alternatename:?zone_info_source_factory@cctz_extension@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@3P6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@@std@@@std@@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ - "@AEBV?$function@$$A6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@@std@@@std@@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@@Z@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ - "@@ZEA=?default_factory@cctz_extension@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@3P6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@@std@@@std@@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ - "@AEBV?$function@$$A6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ - "@@@std@@@std@@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@@Z@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ - "@@ZEA") +#pragma comment( \ + linker, \ + "/alternatename:?zone_info_source_factory@cctz_extension@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@3P6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@@std@@@std@@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ + "@AEBV?$function@$$A6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@@std@@@std@@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@@Z@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ + "@@ZEA=?default_factory@cctz_extension@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@3P6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@@std@@@std@@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ + "@AEBV?$function@$$A6A?AV?$unique_ptr@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \ + "@@@std@@@std@@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@@Z@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \ + "@@ZEA") #else #error Unsupported MSVC platform #endif // _M_<PLATFORM> diff --git a/absl/time/internal/cctz/testdata/version b/absl/time/internal/cctz/testdata/version index 9caed31c..5c8fbb47 100644 --- a/absl/time/internal/cctz/testdata/version +++ b/absl/time/internal/cctz/testdata/version @@ -1 +1 @@ -2022d +2022f diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Bahia_Banderas b/absl/time/internal/cctz/testdata/zoneinfo/America/Bahia_Banderas Binary files differindex cbe22a76..48faea2e 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Bahia_Banderas +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Bahia_Banderas diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Chihuahua b/absl/time/internal/cctz/testdata/zoneinfo/America/Chihuahua Binary files differindex e1780a57..5e0a54f0 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Chihuahua +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Chihuahua diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Ensenada b/absl/time/internal/cctz/testdata/zoneinfo/America/Ensenada Binary files differindex 19ccd357..e8be26b1 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Ensenada +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Ensenada diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Hermosillo b/absl/time/internal/cctz/testdata/zoneinfo/America/Hermosillo Binary files differindex 8283239e..5c92e296 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Hermosillo +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Hermosillo diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Matamoros b/absl/time/internal/cctz/testdata/zoneinfo/America/Matamoros Binary files differindex 722751b2..88cabcd1 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Matamoros +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Matamoros diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Mazatlan b/absl/time/internal/cctz/testdata/zoneinfo/America/Mazatlan Binary files differindex 4c819fab..97d4d36c 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Mazatlan +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Mazatlan diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Merida b/absl/time/internal/cctz/testdata/zoneinfo/America/Merida Binary files differindex d3b0ca12..e5de1131 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Merida +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Merida diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Mexico_City b/absl/time/internal/cctz/testdata/zoneinfo/America/Mexico_City Binary files differindex ffcf8bee..80a415c7 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Mexico_City +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Mexico_City diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Monterrey b/absl/time/internal/cctz/testdata/zoneinfo/America/Monterrey Binary files differindex dea9e3f5..a5822e2c 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Monterrey +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Monterrey diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Nipigon b/absl/time/internal/cctz/testdata/zoneinfo/America/Nipigon Binary files differindex b9f67a9f..fe6be8ea 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Nipigon +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Nipigon diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Ojinaga b/absl/time/internal/cctz/testdata/zoneinfo/America/Ojinaga Binary files differindex da0909cb..560b8674 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Ojinaga +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Ojinaga diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Rainy_River b/absl/time/internal/cctz/testdata/zoneinfo/America/Rainy_River Binary files differindex d6ddda48..7e646d18 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Rainy_River +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Rainy_River diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Santa_Isabel b/absl/time/internal/cctz/testdata/zoneinfo/America/Santa_Isabel Binary files differindex 19ccd357..e8be26b1 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Santa_Isabel +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Santa_Isabel diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Thunder_Bay b/absl/time/internal/cctz/testdata/zoneinfo/America/Thunder_Bay Binary files differindex fcb03280..fe6be8ea 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Thunder_Bay +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Thunder_Bay diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Tijuana b/absl/time/internal/cctz/testdata/zoneinfo/America/Tijuana Binary files differindex 19ccd357..e8be26b1 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Tijuana +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Tijuana diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Amman b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Amman Binary files differindex d97d308d..a3f9dff5 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Amman +++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Amman diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Damascus b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Damascus Binary files differindex 168ef9ba..bd1624de 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Damascus +++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Damascus diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaNorte b/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaNorte Binary files differindex 19ccd357..e8be26b1 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaNorte +++ b/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaNorte diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaSur b/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaSur Binary files differindex 4c819fab..97d4d36c 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaSur +++ b/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaSur diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Mexico/General b/absl/time/internal/cctz/testdata/zoneinfo/Mexico/General Binary files differindex ffcf8bee..80a415c7 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Mexico/General +++ b/absl/time/internal/cctz/testdata/zoneinfo/Mexico/General diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Fiji b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Fiji Binary files differindex 8b2dd52b..610b850b 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Fiji +++ b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Fiji diff --git a/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab b/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab index cf9cf201..75372e3f 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab +++ b/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab @@ -102,12 +102,9 @@ CA +4612-05957 America/Glace_Bay Atlantic - NS (Cape Breton) CA +4606-06447 America/Moncton Atlantic - New Brunswick CA +5320-06025 America/Goose_Bay Atlantic - Labrador (most areas) CA,BS +4339-07923 America/Toronto Eastern - ON, QC (most areas), Bahamas -CA +4901-08816 America/Nipigon Eastern - ON, QC (no DST 1967-73) -CA +4823-08915 America/Thunder_Bay Eastern - ON (Thunder Bay) CA +6344-06828 America/Iqaluit Eastern - NU (most east areas) CA +6608-06544 America/Pangnirtung Eastern - NU (Pangnirtung) CA +4953-09709 America/Winnipeg Central - ON (west); Manitoba -CA +4843-09434 America/Rainy_River Central - ON (Rainy R, Ft Frances) CA +744144-0944945 America/Resolute Central - NU (Resolute) CA +624900-0920459 America/Rankin_Inlet Central - NU (central) CA +5024-10439 America/Regina CST - SK (most areas) diff --git a/absl/types/internal/span.h b/absl/types/internal/span.h index 1920a89e..d653bb2c 100644 --- a/absl/types/internal/span.h +++ b/absl/types/internal/span.h @@ -32,9 +32,6 @@ template <typename T> class Span; namespace span_internal { -// A constexpr min function -constexpr size_t Min(size_t a, size_t b) noexcept { return a < b ? a : b; } - // Wrappers for access to container data pointers. template <typename C> constexpr auto GetDataImpl(C& c, char) noexcept // NOLINT(runtime/references) diff --git a/absl/types/span.h b/absl/types/span.h index cd863a95..d7bdbb1f 100644 --- a/absl/types/span.h +++ b/absl/types/span.h @@ -420,7 +420,7 @@ class Span { // absl::MakeSpan(vec).subspan(5); // throws std::out_of_range constexpr Span subspan(size_type pos = 0, size_type len = npos) const { return (pos <= size()) - ? Span(data() + pos, span_internal::Min(size() - pos, len)) + ? Span(data() + pos, (std::min)(size() - pos, len)) : (base_internal::ThrowStdOutOfRange("pos > size()"), Span()); } |